From 8586ae9ddd6bf3ad4b64af6cc72120a73e1deb5e Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 16:53:46 +0545 Subject: [PATCH 01/66] Add sql for blobber meta table --- sql/14-add-blobber-meta-table.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 sql/14-add-blobber-meta-table.sql diff --git a/sql/14-add-blobber-meta-table.sql b/sql/14-add-blobber-meta-table.sql new file mode 100644 index 000000000..e5940dfb4 --- /dev/null +++ b/sql/14-add-blobber-meta-table.sql @@ -0,0 +1,8 @@ +\connect blobber_meta; + + +CREATE TABLE blobber_meta ( + id BIGSERIAL NOT NULL, + private_key VARCHAR(512) NOT NULL, + public_key VARCHAR(512) NOT NULL +); From 8ef36905329a4559428f4915db11db536a58a6be Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 17:22:34 +0545 Subject: [PATCH 02/66] Add api for marketplace public keys --- code/go/0chain.net/blobbercore/handler/handler.go | 13 +++++++++++++ .../blobbercore/handler/storage_handler.go | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 49da5ac50..dd59b3529 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -57,6 +57,9 @@ func SetupHandlers(r *mux.Router) { r.HandleFunc("/_statsJSON", common.UserRateLimit(common.ToJSONResponse(stats.StatsJSONHandler))) r.HandleFunc("/_cleanupdisk", common.UserRateLimit(common.ToJSONResponse(WithReadOnlyConnection(CleanupDiskHandler)))) r.HandleFunc("/getstats", common.UserRateLimit(common.ToJSONResponse(stats.GetStatsHandler))) + + //marketplace related + r.HandleFunc("/v1/marketplace/public_key", common.UserRateLimit(common.ToJSONResponse(WithConnection(MarketPlacePublicKeyHandler)))) } func WithReadOnlyConnection(handler common.JSONResponderF) common.JSONResponderF { @@ -316,3 +319,13 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro err := CleanupDiskFiles(ctx) return "cleanup", err } + +func MarketPlacePublicKeyHandler(ctx context.Context, r *http.Request) (interface{}, error) { + response, err := storageHandler.GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) + if err != nil { + return nil, err + } + + return response, nil +} + diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index 92b924484..1a795116f 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -753,3 +753,11 @@ func pathHashFromReq(r *http.Request, allocationID string) (string, error) { return pathHash, nil } + +func GetOrCreateMarketplaceEncryptionKeyPair(r *http.Request, ) (string, error) { + data := map[string]string{ + "public_key": "abcd", + } + response, _ := json.Marshal(data) + return response, nil +} From e30411f20370d80acf75315c8a5cfec411aa19af Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 17:42:10 +0545 Subject: [PATCH 03/66] Refactor and add bash scripts for stop --- .../handler/blobber_meta_handler.go | 26 +++++++++++++++++++ .../0chain.net/blobbercore/handler/handler.go | 2 +- .../blobbercore/handler/storage_handler.go | 7 ----- docker.local/bin/blobber.stop_bls.sh | 11 ++++++++ docker.local/bin/stop_all.sh | 12 +++++++++ 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go create mode 100755 docker.local/bin/blobber.stop_bls.sh create mode 100755 docker.local/bin/stop_all.sh diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go new file mode 100644 index 000000000..89b0c619f --- /dev/null +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -0,0 +1,26 @@ +// +build !integration_tests + +package handler + +import ( + "net/http" + + // "0chain.net/blobbercore/config" + // "0chain.net/blobbercore/constants" + // "0chain.net/blobbercore/datastore" + // "0chain.net/blobbercore/stats" + // "0chain.net/core/common" + + // . "0chain.net/core/logging" + // "go.uber.org/zap" + + // "github.com/gorilla/mux" +) + + +func GetOrCreateMarketplaceEncryptionKeyPair(r *http.Request) (interface{}, error) { + data := map[string]string{ + "public_key": "abcd", + } + return data, nil +} diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index dd59b3529..184b9c1ba 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -321,7 +321,7 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro } func MarketPlacePublicKeyHandler(ctx context.Context, r *http.Request) (interface{}, error) { - response, err := storageHandler.GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) + response, err := GetOrCreateMarketplaceEncryptionKeyPair(r) if err != nil { return nil, err } diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index 1a795116f..e3bd5c6f9 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -754,10 +754,3 @@ func pathHashFromReq(r *http.Request, allocationID string) (string, error) { return pathHash, nil } -func GetOrCreateMarketplaceEncryptionKeyPair(r *http.Request, ) (string, error) { - data := map[string]string{ - "public_key": "abcd", - } - response, _ := json.Marshal(data) - return response, nil -} diff --git a/docker.local/bin/blobber.stop_bls.sh b/docker.local/bin/blobber.stop_bls.sh new file mode 100755 index 000000000..a0b60b3e5 --- /dev/null +++ b/docker.local/bin/blobber.stop_bls.sh @@ -0,0 +1,11 @@ +#!/bin/sh +PWD=`pwd` +BLOBBER_DIR=`basename $PWD` +BLOBBER_ID=`echo my directory $BLOBBER_DIR | sed -e 's/.*\(.\)$/\1/'` + + +echo Stopping blobber$BLOBBER_ID ... + +# echo blobber$i + +BLOBBER=$BLOBBER_ID docker-compose -p blobber$BLOBBER_ID -f ../b0docker-compose.yml down diff --git a/docker.local/bin/stop_all.sh b/docker.local/bin/stop_all.sh new file mode 100755 index 000000000..b44c473a4 --- /dev/null +++ b/docker.local/bin/stop_all.sh @@ -0,0 +1,12 @@ +#!/bin/bash +cd docker.local/blobber1 +../bin/blobber.stop_bls.sh +cd - +cd docker.local/blobber2 +../bin/blobber.stop_bls.sh +cd - +cd docker.local/blobber3 +../bin/blobber.stop_bls.sh +cd - +cd docker.local/blobber4 +../bin/blobber.stop_bls.sh From 9ff0170be7eaeb70aef0cde7b0bc7342a537776e Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 19:44:25 +0545 Subject: [PATCH 04/66] Update table name --- ...add-blobber-meta-table.sql => 14-add-marketplace-table.sql} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename sql/{14-add-blobber-meta-table.sql => 14-add-marketplace-table.sql} (65%) diff --git a/sql/14-add-blobber-meta-table.sql b/sql/14-add-marketplace-table.sql similarity index 65% rename from sql/14-add-blobber-meta-table.sql rename to sql/14-add-marketplace-table.sql index e5940dfb4..549dd89a2 100644 --- a/sql/14-add-blobber-meta-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -1,8 +1,7 @@ \connect blobber_meta; -CREATE TABLE blobber_meta ( - id BIGSERIAL NOT NULL, +CREATE TABLE marketplace ( private_key VARCHAR(512) NOT NULL, public_key VARCHAR(512) NOT NULL ); From dae9b423233532047e62c16d2fce9e845360a4b3 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 21:59:35 +0545 Subject: [PATCH 05/66] Add keypair to database --- .../0chain.net/blobbercore/config/config.go | 3 + .../blobbercore/reference/marketplace.go | 61 +++++++++++++++++++ .../blobbercore/reference/marketplace_test.go | 21 +++++++ 3 files changed, 85 insertions(+) create mode 100644 code/go/0chain.net/blobbercore/reference/marketplace.go create mode 100644 code/go/0chain.net/blobbercore/reference/marketplace_test.go diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index bf95a8d8f..77061172a 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -41,6 +41,7 @@ func SetupDefaultConfig() { viper.SetDefault("service_charge", 0.3) viper.SetDefault("update_allocations_interval", time.Duration(-1)) + viper.SetDefault("signature_scheme", "ed25519") } /*SetupConfig - setup the configuration system */ @@ -118,6 +119,8 @@ type Config struct { NumDelegates int `json:"num_delegates"` // ServiceCharge for blobber. ServiceCharge float64 `json:"service_charge"` + + SignatureScheme string `json:"signature_scheme"` } /*Configuration of the system */ diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go new file mode 100644 index 000000000..fe8ac3a0b --- /dev/null +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -0,0 +1,61 @@ +package reference + +import ( + "context" + + "0chain.net/blobbercore/config" + "0chain.net/blobbercore/datastore" + "github.com/0chain/gosdk/core/zcncrypto" +) + +type MarketplaceInfo struct { + PublicKey string `gorm:"public_key" json:"public_key"` + PrivateKey string `gorm:"private_key" json:"private_key,omitempty"` +} + +func TableName() string { + return "marketplace" +} + +func AddEncryptionKeyPair(ctx context.Context, privateKey string, publicKey string) error { + db := datastore.GetStore().GetTransaction(ctx) + return db.Create(&MarketplaceInfo{ + PrivateKey: privateKey, + PublicKey: publicKey, + }).Error +} + +func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { + db := datastore.GetStore().GetTransaction(ctx) + marketplaceInfo := MarketplaceInfo{} + err := db.Table(TableName()).First(&MarketplaceInfo{}).Error + return marketplaceInfo, err +} + +func GetSecretKeyPair() (*zcncrypto.KeyPair, error) { + sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) + wallet, err := sigScheme.GenerateKeys() + if err != nil { + return nil, err + } + return &wallet.Keys[0], nil +} + +func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { + row, err := GetMarketplaceInfo(ctx) + if err == nil { + return &row, err + } + + keyPair, err := GetSecretKeyPair() + if err != nil { + return nil, err + } + + AddEncryptionKeyPair(ctx, keyPair.PrivateKey, keyPair.PublicKey) + + return &MarketplaceInfo{ + PublicKey: keyPair.PublicKey, + PrivateKey: keyPair.PrivateKey, + }, nil +} diff --git a/code/go/0chain.net/blobbercore/reference/marketplace_test.go b/code/go/0chain.net/blobbercore/reference/marketplace_test.go new file mode 100644 index 000000000..9bf60ce66 --- /dev/null +++ b/code/go/0chain.net/blobbercore/reference/marketplace_test.go @@ -0,0 +1,21 @@ +package reference + +import ( + "0chain.net/blobbercore/config" + "github.com/stretchr/testify/require" + "fmt" + "testing" +) + +func TestSecretKeys(t *testing.T) { + config.Configuration = config.Config{ + SignatureScheme: "ed25519", + } + result, err := GetSecretKeyPair() + + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.PublicKey) + require.NotNil(t, result.PrivateKey) + fmt.Println(result.PublicKey, result.PrivateKey) +} From fa10267050a9bb18d00f5ba566b74bac13544695 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 17 May 2021 22:11:51 +0545 Subject: [PATCH 06/66] Use db reference for api response --- .../blobbercore/handler/blobber_meta_handler.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go index 89b0c619f..8eb83fd90 100644 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -15,12 +15,13 @@ import ( // "go.uber.org/zap" // "github.com/gorilla/mux" + + "0chain.net/blobbercore/reference" ) func GetOrCreateMarketplaceEncryptionKeyPair(r *http.Request) (interface{}, error) { - data := map[string]string{ - "public_key": "abcd", - } - return data, nil + info, err := reference.GetOrCreateMarketplaceInfo(ctx) + + return info, err } From 61f18e21e9cce33c925e1b25e7ea180f3b5fefd7 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 18 May 2021 12:52:02 +0545 Subject: [PATCH 07/66] Add api test --- .../handler/blobber_meta_handler.go | 3 +- .../0chain.net/blobbercore/handler/handler.go | 2 +- .../blobbercore/handler/handler_test.go | 54 +++++++++++++++++++ .../blobbercore/reference/marketplace.go | 3 +- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go index 8eb83fd90..f8f127794 100644 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -3,6 +3,7 @@ package handler import ( + "context" "net/http" // "0chain.net/blobbercore/config" @@ -20,7 +21,7 @@ import ( ) -func GetOrCreateMarketplaceEncryptionKeyPair(r *http.Request) (interface{}, error) { +func GetOrCreateMarketplaceEncryptionKeyPair(ctx context.Context, r *http.Request) (interface{}, error) { info, err := reference.GetOrCreateMarketplaceInfo(ctx) return info, err diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 184b9c1ba..8e5ac7611 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -321,7 +321,7 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro } func MarketPlacePublicKeyHandler(ctx context.Context, r *http.Request) (interface{}, error) { - response, err := GetOrCreateMarketplaceEncryptionKeyPair(r) + response, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) if err != nil { return nil, err } diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 0f613734d..12ce633e6 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -170,6 +170,15 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(uName) + marketplacePath := "/v1/marketplace/public_key" + mName := "Marketplace" + router.HandleFunc(marketplacePath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(MarketPlacePublicKeyHandler), + ), + ), + ).Name(mName) + return router, map[string]string{ opPath: opName, @@ -181,6 +190,7 @@ func setupHandlers() (*mux.Router, map[string]string) { cPath: cName, aPath: aName, uPath: uName, + marketplacePath: mName, } } @@ -193,6 +203,50 @@ func isEndpointAllowGetReq(name string) bool { } } +func TestMarketplaceApi(t *testing.T) { + router, handlers := setupHandlers() + + t.Run("marketplace_key_existing", func(t *testing.T) { + mock := datastore.MockTheStore(t) + setupDbMock := func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."public_key" LIMIT 1`)). + WillReturnRows( + sqlmock.NewRows([]string{"public_key", "private_key"}). + AddRow("pub", "prv"), + ) + + mock.ExpectCommit() + } + setupDbMock(mock) + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/public_key"] + + url, err := router.Get(handlerName).URL() + logging.Logger.Info(url.String()) + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + wantBody := `{"public_key":"pub","private_key":"prv"}` + "\n" + assert.Equal(t, wantBody, recorder.Body.String()) + }) + + +} + func TestHandlers_Requiring_Signature(t *testing.T) { setup(t) diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index fe8ac3a0b..3ba4fa8c7 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -2,7 +2,6 @@ package reference import ( "context" - "0chain.net/blobbercore/config" "0chain.net/blobbercore/datastore" "github.com/0chain/gosdk/core/zcncrypto" @@ -28,7 +27,7 @@ func AddEncryptionKeyPair(ctx context.Context, privateKey string, publicKey stri func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { db := datastore.GetStore().GetTransaction(ctx) marketplaceInfo := MarketplaceInfo{} - err := db.Table(TableName()).First(&MarketplaceInfo{}).Error + err := db.Table(TableName()).First(&marketplaceInfo).Error return marketplaceInfo, err } From c3cbd1d16aed65f61c192143e2fe6b432fbe0a08 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 18 May 2021 13:25:14 +0545 Subject: [PATCH 08/66] Fix bug and add test for insert --- .../blobbercore/handler/handler_test.go | 42 ++++++++++++++++++- .../blobbercore/reference/marketplace.go | 2 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 12ce633e6..7b8008682 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -42,6 +42,7 @@ func init() { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) + bconfig.Configuration.SignatureScheme = "bls0chain" } func setup(t *testing.T) { @@ -224,7 +225,6 @@ func TestMarketplaceApi(t *testing.T) { handlerName := handlers["/v1/marketplace/public_key"] url, err := router.Get(handlerName).URL() - logging.Logger.Info(url.String()) if err != nil { t.Fatal() } @@ -244,6 +244,46 @@ func TestMarketplaceApi(t *testing.T) { assert.Equal(t, wantBody, recorder.Body.String()) }) + t.Run("marketplace_create_new_key_and_return", func(t *testing.T) { + mock := datastore.MockTheStore(t) + setupDbMock := func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."public_key" LIMIT 1`)). + WillReturnRows( + sqlmock.NewRows([]string{"public_key", "private_key"}), + ) + + mock.ExpectExec(`INSERT INTO "marketplace"`). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(0, 0)) + + + mock.ExpectCommit() + } + setupDbMock(mock) + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/public_key"] + + url, err := router.Get(handlerName).URL() + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + // wantBody := `{"public_key":"pub","private_key":"prv"}` + "\n" + // assert.Equal(t, wantBody, recorder.Body.String()) + }) } diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index 3ba4fa8c7..bf8674f87 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -18,7 +18,7 @@ func TableName() string { func AddEncryptionKeyPair(ctx context.Context, privateKey string, publicKey string) error { db := datastore.GetStore().GetTransaction(ctx) - return db.Create(&MarketplaceInfo{ + return db.Table(TableName()).Create(&MarketplaceInfo{ PrivateKey: privateKey, PublicKey: publicKey, }).Error From 9053c885e528cbb04d160f1b6001cc9a71a71744 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 18 May 2021 16:48:34 +0545 Subject: [PATCH 09/66] Updates --- code/go/0chain.net/blobbercore/config/config.go | 3 --- .../0chain.net/blobbercore/handler/handler_test.go | 13 +++++++++---- .../0chain.net/blobbercore/reference/marketplace.go | 11 +++++++++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index 77061172a..bf95a8d8f 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -41,7 +41,6 @@ func SetupDefaultConfig() { viper.SetDefault("service_charge", 0.3) viper.SetDefault("update_allocations_interval", time.Duration(-1)) - viper.SetDefault("signature_scheme", "ed25519") } /*SetupConfig - setup the configuration system */ @@ -119,8 +118,6 @@ type Config struct { NumDelegates int `json:"num_delegates"` // ServiceCharge for blobber. ServiceCharge float64 `json:"service_charge"` - - SignatureScheme string `json:"signature_scheme"` } /*Configuration of the system */ diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 7b8008682..5104bce34 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -14,6 +14,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zcncore" "github.com/DATA-DOG/go-sqlmock" @@ -42,7 +43,6 @@ func init() { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) - bconfig.Configuration.SignatureScheme = "bls0chain" } func setup(t *testing.T) { @@ -172,7 +172,7 @@ func setupHandlers() (*mux.Router, map[string]string) { ).Name(uName) marketplacePath := "/v1/marketplace/public_key" - mName := "Marketplace" + mName := "MarketplaceInfo" router.HandleFunc(marketplacePath, common.UserRateLimit( common.ToJSONResponse( WithReadOnlyConnection(MarketPlacePublicKeyHandler), @@ -205,6 +205,7 @@ func isEndpointAllowGetReq(name string) bool { } func TestMarketplaceApi(t *testing.T) { + setup(t) router, handlers := setupHandlers() t.Run("marketplace_key_existing", func(t *testing.T) { @@ -281,8 +282,12 @@ func TestMarketplaceApi(t *testing.T) { recorder := httptest.NewRecorder() router.ServeHTTP(recorder, httprequest) assert.Equal(t, 200, 200) - // wantBody := `{"public_key":"pub","private_key":"prv"}` + "\n" - // assert.Equal(t, wantBody, recorder.Body.String()) + marketplaceInfo := reference.MarketplaceInfo {} + json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) + assert.NotEmpty(t, marketplaceInfo) + assert.NotEmpty(t, marketplaceInfo.PublicKey) + assert.NotEmpty(t, marketplaceInfo.PrivateKey) + fmt.Println(marketplaceInfo) }) } diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index bf8674f87..72eb3073c 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -2,9 +2,11 @@ package reference import ( "context" - "0chain.net/blobbercore/config" + "0chain.net/core/config" "0chain.net/blobbercore/datastore" "github.com/0chain/gosdk/core/zcncrypto" + . "0chain.net/core/logging" + "go.uber.org/zap" ) type MarketplaceInfo struct { @@ -32,7 +34,9 @@ func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { } func GetSecretKeyPair() (*zcncrypto.KeyPair, error) { - sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) + //sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) + // TODO: bls0chain scheme crashes + sigScheme := zcncrypto.NewSignatureScheme("ed25519") wallet, err := sigScheme.GenerateKeys() if err != nil { return nil, err @@ -46,7 +50,10 @@ func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { return &row, err } + Logger.Info("Creating key pair", zap.String("signature_scheme", config.Configuration.SignatureScheme)) keyPair, err := GetSecretKeyPair() + Logger.Info("Secret key pair created") + if err != nil { return nil, err } From 5b7f7edb80057abb1705f39bda2d8bae21d3d013 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 18 May 2021 16:56:10 +0545 Subject: [PATCH 10/66] Hide private keys from api --- .../0chain.net/blobbercore/handler/blobber_meta_handler.go | 2 +- code/go/0chain.net/blobbercore/handler/handler.go | 6 ++++-- code/go/0chain.net/blobbercore/handler/handler_test.go | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go index f8f127794..c56ceb645 100644 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -21,7 +21,7 @@ import ( ) -func GetOrCreateMarketplaceEncryptionKeyPair(ctx context.Context, r *http.Request) (interface{}, error) { +func GetOrCreateMarketplaceEncryptionKeyPair(ctx context.Context, r *http.Request) (*reference.MarketplaceInfo, error) { info, err := reference.GetOrCreateMarketplaceInfo(ctx) return info, err diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 8e5ac7611..07a25fd05 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -321,11 +321,13 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro } func MarketPlacePublicKeyHandler(ctx context.Context, r *http.Request) (interface{}, error) { - response, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) + marketplaceInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) if err != nil { return nil, err } - return response, nil + return map[string]string { + "public_key": marketplaceInfo.PublicKey, + }, nil } diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 5104bce34..63d805262 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -241,7 +241,7 @@ func TestMarketplaceApi(t *testing.T) { recorder := httptest.NewRecorder() router.ServeHTTP(recorder, httprequest) assert.Equal(t, 200, 200) - wantBody := `{"public_key":"pub","private_key":"prv"}` + "\n" + wantBody := `{"public_key":"pub"}` + "\n" assert.Equal(t, wantBody, recorder.Body.String()) }) @@ -286,7 +286,7 @@ func TestMarketplaceApi(t *testing.T) { json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) assert.NotEmpty(t, marketplaceInfo) assert.NotEmpty(t, marketplaceInfo.PublicKey) - assert.NotEmpty(t, marketplaceInfo.PrivateKey) + assert.Empty(t, marketplaceInfo.PrivateKey) fmt.Println(marketplaceInfo) }) From f51bc71f0aa8894e24040f44f0d28693b0adf020 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 19 May 2021 19:40:53 +0545 Subject: [PATCH 11/66] Add mnemonic to table --- .../0chain.net/blobbercore/handler/handler.go | 6 +-- .../blobbercore/handler/handler_test.go | 23 ++++++------ .../blobbercore/reference/marketplace.go | 37 +++++++++++++------ sql/14-add-marketplace-table.sql | 3 +- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 07a25fd05..3fa95a218 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -59,7 +59,7 @@ func SetupHandlers(r *mux.Router) { r.HandleFunc("/getstats", common.UserRateLimit(common.ToJSONResponse(stats.GetStatsHandler))) //marketplace related - r.HandleFunc("/v1/marketplace/public_key", common.UserRateLimit(common.ToJSONResponse(WithConnection(MarketPlacePublicKeyHandler)))) + r.HandleFunc("/v1/marketplace/secret", common.UserRateLimit(common.ToJSONResponse(WithConnection(MarketPlaceSecretHandler)))) } func WithReadOnlyConnection(handler common.JSONResponderF) common.JSONResponderF { @@ -320,14 +320,14 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro return "cleanup", err } -func MarketPlacePublicKeyHandler(ctx context.Context, r *http.Request) (interface{}, error) { +func MarketPlaceSecretHandler(ctx context.Context, r *http.Request) (interface{}, error) { marketplaceInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) if err != nil { return nil, err } return map[string]string { - "public_key": marketplaceInfo.PublicKey, + "mnemonic": marketplaceInfo.Mnemonic, }, nil } diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 63d805262..8f1330a29 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -171,11 +171,11 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(uName) - marketplacePath := "/v1/marketplace/public_key" + marketplacePath := "/v1/marketplace/secret" mName := "MarketplaceInfo" router.HandleFunc(marketplacePath, common.UserRateLimit( common.ToJSONResponse( - WithReadOnlyConnection(MarketPlacePublicKeyHandler), + WithReadOnlyConnection(MarketPlaceSecretHandler), ), ), ).Name(mName) @@ -213,17 +213,17 @@ func TestMarketplaceApi(t *testing.T) { setupDbMock := func(mock sqlmock.Sqlmock) { mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."public_key" LIMIT 1`)). + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key"}). - AddRow("pub", "prv"), + sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}). + AddRow("pub", "prv", "a b c d"), ) mock.ExpectCommit() } setupDbMock(mock) httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/public_key"] + handlerName := handlers["/v1/marketplace/secret"] url, err := router.Get(handlerName).URL() if err != nil { @@ -241,7 +241,7 @@ func TestMarketplaceApi(t *testing.T) { recorder := httptest.NewRecorder() router.ServeHTTP(recorder, httprequest) assert.Equal(t, 200, 200) - wantBody := `{"public_key":"pub"}` + "\n" + wantBody := `{"mnemonic":"a b c d"}` + "\n" assert.Equal(t, wantBody, recorder.Body.String()) }) @@ -250,9 +250,9 @@ func TestMarketplaceApi(t *testing.T) { setupDbMock := func(mock sqlmock.Sqlmock) { mock.ExpectBegin() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."public_key" LIMIT 1`)). + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key"}), + sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}), ) mock.ExpectExec(`INSERT INTO "marketplace"`). @@ -264,7 +264,7 @@ func TestMarketplaceApi(t *testing.T) { } setupDbMock(mock) httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/public_key"] + handlerName := handlers["/v1/marketplace/secret"] url, err := router.Get(handlerName).URL() if err != nil { @@ -285,8 +285,9 @@ func TestMarketplaceApi(t *testing.T) { marketplaceInfo := reference.MarketplaceInfo {} json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) assert.NotEmpty(t, marketplaceInfo) - assert.NotEmpty(t, marketplaceInfo.PublicKey) + assert.Empty(t, marketplaceInfo.PublicKey) assert.Empty(t, marketplaceInfo.PrivateKey) + assert.NotEmpty(t, marketplaceInfo.Mnemonic) fmt.Println(marketplaceInfo) }) diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index 72eb3073c..5a0231c5d 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -1,28 +1,36 @@ package reference import ( - "context" - "0chain.net/core/config" "0chain.net/blobbercore/datastore" - "github.com/0chain/gosdk/core/zcncrypto" + "0chain.net/core/config" . "0chain.net/core/logging" + "context" + "github.com/0chain/gosdk/core/zcncrypto" "go.uber.org/zap" ) type MarketplaceInfo struct { + Mnemonic string `gorm:"mnemonic" json:"mnemonic,omitempty"` PublicKey string `gorm:"public_key" json:"public_key"` PrivateKey string `gorm:"private_key" json:"private_key,omitempty"` } +type KeyPairInfo struct { + PublicKey string + PrivateKey string + Mnemonic string +} + func TableName() string { return "marketplace" } -func AddEncryptionKeyPair(ctx context.Context, privateKey string, publicKey string) error { +func AddEncryptionKeyPairInfo(ctx context.Context, keyPairInfo KeyPairInfo) error { db := datastore.GetStore().GetTransaction(ctx) return db.Table(TableName()).Create(&MarketplaceInfo{ - PrivateKey: privateKey, - PublicKey: publicKey, + PrivateKey: keyPairInfo.PrivateKey, + PublicKey: keyPairInfo.PublicKey, + Mnemonic: keyPairInfo.Mnemonic, }).Error } @@ -33,7 +41,7 @@ func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { return marketplaceInfo, err } -func GetSecretKeyPair() (*zcncrypto.KeyPair, error) { +func GetSecretKeyPair() (*KeyPairInfo, error) { //sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) // TODO: bls0chain scheme crashes sigScheme := zcncrypto.NewSignatureScheme("ed25519") @@ -41,7 +49,11 @@ func GetSecretKeyPair() (*zcncrypto.KeyPair, error) { if err != nil { return nil, err } - return &wallet.Keys[0], nil + return &KeyPairInfo { + PublicKey: wallet.Keys[0].PublicKey, + PrivateKey: wallet.Keys[0].PrivateKey, + Mnemonic: wallet.Mnemonic, + }, nil } func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { @@ -51,17 +63,18 @@ func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { } Logger.Info("Creating key pair", zap.String("signature_scheme", config.Configuration.SignatureScheme)) - keyPair, err := GetSecretKeyPair() + keyPairInfo, err := GetSecretKeyPair() Logger.Info("Secret key pair created") if err != nil { return nil, err } - AddEncryptionKeyPair(ctx, keyPair.PrivateKey, keyPair.PublicKey) + AddEncryptionKeyPairInfo(ctx, *keyPairInfo) return &MarketplaceInfo{ - PublicKey: keyPair.PublicKey, - PrivateKey: keyPair.PrivateKey, + PrivateKey: keyPairInfo.PrivateKey, + PublicKey: keyPairInfo.PublicKey, + Mnemonic: keyPairInfo.Mnemonic, }, nil } diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index 549dd89a2..d532a5905 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -3,5 +3,6 @@ CREATE TABLE marketplace ( private_key VARCHAR(512) NOT NULL, - public_key VARCHAR(512) NOT NULL + public_key VARCHAR(512) NOT NULL, + mnemonic VARCHAR(512) NOT NULL, ); From 0ea4fb0ca16a52ad52593ae4428b75880022c9dd Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 19 May 2021 21:51:25 +0545 Subject: [PATCH 12/66] Fix permission --- sql/14-add-marketplace-table.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index d532a5905..68e350ed4 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -4,5 +4,7 @@ CREATE TABLE marketplace ( private_key VARCHAR(512) NOT NULL, public_key VARCHAR(512) NOT NULL, - mnemonic VARCHAR(512) NOT NULL, + mnemonic VARCHAR(512) NOT NULL ); + +GRANT ALL PRIVILEGES ON TABLE marketplace TO blobber_user; From de960e7a5813c1530e0ff0c1c20a90fab449bedf Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 21 May 2021 21:17:39 +0545 Subject: [PATCH 13/66] Fix blobber commit ref --- .../blobbercore/reference/ds_test.go | 26 +++++++++++++++++++ .../blobbercore/reference/marketplace_test.go | 6 +---- .../0chain.net/blobbercore/reference/ref.go | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 code/go/0chain.net/blobbercore/reference/ds_test.go diff --git a/code/go/0chain.net/blobbercore/reference/ds_test.go b/code/go/0chain.net/blobbercore/reference/ds_test.go new file mode 100644 index 000000000..caca35bb3 --- /dev/null +++ b/code/go/0chain.net/blobbercore/reference/ds_test.go @@ -0,0 +1,26 @@ +package reference + +import ( + "0chain.net/blobbercore/config" + "0chain.net/blobbercore/datastore" + "fmt" + "testing" +) + +func TestMockDb(t *testing.T) { + config.Configuration.DBHost = "localhost" + config.Configuration.DBName = "blobber_meta" + config.Configuration.DBPort = "5431" + config.Configuration.DBUserName = "blobber_user" + config.Configuration.DBPassword = "" + + datastore.GetStore().Open() + db := datastore.GetStore().GetDB() + ref := &Ref{} + err := db.Where(&Ref{AllocationID: "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367", Path: "/hack.txt"}).First(ref).Error + if err != nil { + fmt.Println("err", err) + return + } + fmt.Println(string(ref.Attributes)) +} diff --git a/code/go/0chain.net/blobbercore/reference/marketplace_test.go b/code/go/0chain.net/blobbercore/reference/marketplace_test.go index 9bf60ce66..faa841ac9 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace_test.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace_test.go @@ -1,16 +1,12 @@ package reference import ( - "0chain.net/blobbercore/config" - "github.com/stretchr/testify/require" "fmt" + "github.com/stretchr/testify/require" "testing" ) func TestSecretKeys(t *testing.T) { - config.Configuration = config.Config{ - SignatureScheme: "ed25519", - } result, err := GetSecretKeyPair() require.NoError(t, err) diff --git a/code/go/0chain.net/blobbercore/reference/ref.go b/code/go/0chain.net/blobbercore/reference/ref.go index f422f501f..260b8a109 100644 --- a/code/go/0chain.net/blobbercore/reference/ref.go +++ b/code/go/0chain.net/blobbercore/reference/ref.go @@ -35,6 +35,7 @@ type Attributes struct { // or a 3rd party user. It affects read operations only. It requires // blobbers to be trusted. WhoPaysForReads common.WhoPays `json:"who_pays_for_reads,omitempty"` + PreAtBlobber bool `json:"pre_at_blobber,omitempty"` // add more file / directory attributes by needs with // 'omitempty' json tag to avoid hash difference for From 8d567ace204b67e3ef5ba75d13065d500a28663d Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 21 May 2021 22:19:52 +0545 Subject: [PATCH 14/66] Add test for invalid signature --- .../0chain.net/core/encryption/keys_test.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 code/go/0chain.net/core/encryption/keys_test.go diff --git a/code/go/0chain.net/core/encryption/keys_test.go b/code/go/0chain.net/core/encryption/keys_test.go new file mode 100644 index 000000000..c8ee3d092 --- /dev/null +++ b/code/go/0chain.net/core/encryption/keys_test.go @@ -0,0 +1,23 @@ +package encryption + +import ( + "github.com/0chain/gosdk/zboxcore/client" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSignatureVerify(t *testing.T) { + allocationId := "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367" + walletConfig := "{\"client_id\":\"9a566aa4f8e8c342fed97c8928040a21f21b8f574e5782c28568635ba9c75a85\",\"client_key\":\"40cd10039913ceabacf05a7c60e1ad69bb2964987bc50f77495e514dc451f907c3d8ebcdab20eedde9c8f39b9a1d66609a637352f318552fb69d4b3672516d1a\",\"keys\":[{\"public_key\":\"40cd10039913ceabacf05a7c60e1ad69bb2964987bc50f77495e514dc451f907c3d8ebcdab20eedde9c8f39b9a1d66609a637352f318552fb69d4b3672516d1a\",\"private_key\":\"a3a88aad5d89cec28c6e37c2925560ce160ac14d2cdcf4a4654b2bb358fe7514\"}],\"mnemonics\":\"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown\",\"version\":\"1.0\",\"date_created\":\"2021-05-21 17:32:29.484657 +0545 +0545 m=+0.072791323\"}" + client.PopulateClient(walletConfig, "bls0chain") + sig, serr := client.Sign(allocationId) + assert.Nil(t, serr) + assert.NotNil(t, sig) + + res, err := client.VerifySignature( + "fb0eb9351978091da350348211888b06ed1ce84ae40d08de3cc826cd85197188", + allocationId, + ) + assert.Nil(t, err) + assert.Equal(t, res, true) +} \ No newline at end of file From 356f6e7a20cbbfbdc651d22eb0af3a0522117199 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 17:27:25 +0545 Subject: [PATCH 15/66] Fix verifysignature by removing encryption.hash --- code/go/0chain.net/blobbercore/handler/storage_handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index e3bd5c6f9..30f1db7a1 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -711,8 +711,7 @@ func verifySignatureFromRequest(r *http.Request, pbK string) (bool, error) { return false, common.NewError("invalid_params", "Missing allocation tx") } - hash := encryption.Hash(data) - return encryption.Verify(pbK, sign, hash) + return encryption.Verify(pbK, sign, data) } // pathsFromReq retrieves paths value from request which can be represented as single "path" value or "paths" values, From 176e4cd7e1f611d1f5787fd025e787ac9d443af4 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 17:30:03 +0545 Subject: [PATCH 16/66] Remove unused import --- .../blobbercore/handler/blobber_meta_handler.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go index c56ceb645..0e6a7245e 100644 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -6,17 +6,6 @@ import ( "context" "net/http" - // "0chain.net/blobbercore/config" - // "0chain.net/blobbercore/constants" - // "0chain.net/blobbercore/datastore" - // "0chain.net/blobbercore/stats" - // "0chain.net/core/common" - - // . "0chain.net/core/logging" - // "go.uber.org/zap" - - // "github.com/gorilla/mux" - "0chain.net/blobbercore/reference" ) From 8c5caf48dbcf744d855949b9dde39a30a183eab2 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 18:56:14 +0545 Subject: [PATCH 17/66] Add dev files --- code/go/0chain.net/blobber/main.go | 4 ++ .../0chain.net/blobbercore/config/config.go | 7 +++ .../handler/blobber_meta_handler.go | 5 ++ .../blobbercore/reference/marketplace.go | 24 ++++++++-- config/0chain_blobber.yaml | 7 ++- docker.local/Dockerfile.dev | 48 +++++++++++++++++++ docker.local/ValidatorDockerfile.dev | 46 ++++++++++++++++++ docker.local/bin/build.blobber.dev.sh | 15 ++++++ 8 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 docker.local/Dockerfile.dev create mode 100644 docker.local/ValidatorDockerfile.dev create mode 100755 docker.local/bin/build.blobber.dev.sh diff --git a/code/go/0chain.net/blobber/main.go b/code/go/0chain.net/blobber/main.go index 322c27232..5133f73d0 100644 --- a/code/go/0chain.net/blobber/main.go +++ b/code/go/0chain.net/blobber/main.go @@ -96,6 +96,10 @@ func SetupWorkerConfig() { config.Configuration.MinLockDemand = viper.GetFloat64("min_lock_demand") config.Configuration.MaxOfferDuration = viper.GetDuration("max_offer_duration") config.Configuration.ChallengeCompletionTime = viper.GetDuration("challenge_completion_time") + config.Configuration.PreEncryption.AutoGenerate = viper.GetBool("pre_encryption.autogenerate") + config.Configuration.PreEncryption.Mnemonic = viper.GetString("pre_encryption.mnemonic") + Logger.Panic("pre encryption config", zap.Bool("autogen", config.Configuration.PreEncryption.AutoGenerate)) + Logger.Panic("pre encryption mnemonic", zap.String("mnemonic", config.Configuration.PreEncryption.Mnemonic)) config.Configuration.ReadLockTimeout = int64( viper.GetDuration("read_lock_timeout") / time.Second, diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index bf95a8d8f..d147f6684 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -41,6 +41,7 @@ func SetupDefaultConfig() { viper.SetDefault("service_charge", 0.3) viper.SetDefault("update_allocations_interval", time.Duration(-1)) + viper.SetDefault("pre_encryption.autogenerate", true) } /*SetupConfig - setup the configuration system */ @@ -63,6 +64,11 @@ const ( DeploymentMainNet = 2 ) +type PreEncryptionConfig struct { + AutoGenerate bool `json:"autogenerate,omitempty"` + Mnemonic string `json:"mnemonic,omitempty"` +} + type Config struct { *config.Config DBHost string @@ -118,6 +124,7 @@ type Config struct { NumDelegates int `json:"num_delegates"` // ServiceCharge for blobber. ServiceCharge float64 `json:"service_charge"` + PreEncryption PreEncryptionConfig } /*Configuration of the system */ diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go index 0e6a7245e..e3d7ea96e 100644 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go @@ -3,6 +3,7 @@ package handler import ( + "0chain.net/blobbercore/config" "context" "net/http" @@ -11,6 +12,10 @@ import ( func GetOrCreateMarketplaceEncryptionKeyPair(ctx context.Context, r *http.Request) (*reference.MarketplaceInfo, error) { + if !config.Configuration.PreEncryption.AutoGenerate { + mnemonic := config.Configuration.PreEncryption.Mnemonic + return reference.GetMarketplaceInfoFromMnemonic(mnemonic), nil + } info, err := reference.GetOrCreateMarketplaceInfo(ctx) return info, err diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index 5a0231c5d..a8b35c82f 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -6,6 +6,7 @@ import ( . "0chain.net/core/logging" "context" "github.com/0chain/gosdk/core/zcncrypto" + zboxenc "github.com/0chain/gosdk/zboxcore/encryption" "go.uber.org/zap" ) @@ -41,11 +42,28 @@ func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { return marketplaceInfo, err } +func GetSignatureScheme() zcncrypto.SignatureScheme { + // TODO: bls0chain scheme crashes + return zcncrypto.NewSignatureScheme("ed25519") +} + +func GetMarketplaceInfoFromMnemonic(mnemonic string) *MarketplaceInfo { + encscheme := zboxenc.NewEncryptionScheme() + encscheme.Initialize(mnemonic) + + PrivateKey, _ := encscheme.GetPrivateKey() + PublicKey, _ := encscheme.GetPublicKey() + + return &MarketplaceInfo{ + PrivateKey: PrivateKey, + PublicKey: PublicKey, + Mnemonic: mnemonic, + } +} + func GetSecretKeyPair() (*KeyPairInfo, error) { //sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) - // TODO: bls0chain scheme crashes - sigScheme := zcncrypto.NewSignatureScheme("ed25519") - wallet, err := sigScheme.GenerateKeys() + wallet, err := zcncrypto.NewSignatureScheme("ed25519").GenerateKeys() if err != nil { return nil, err } diff --git a/config/0chain_blobber.yaml b/config/0chain_blobber.yaml index b43b2502d..df126b630 100755 --- a/config/0chain_blobber.yaml +++ b/config/0chain_blobber.yaml @@ -42,7 +42,7 @@ max_file_size: 10485760 #10MB update_allocations_interval: 1m # delegate wallet (must be set) -delegate_wallet: '8b87739cd6c966c150a8a6e7b327435d4a581d9d9cc1d86a88c8a13ae1ad7a96' +delegate_wallet: '9a566aa4f8e8c342fed97c8928040a21f21b8f574e5782c28568635ba9c75a85' # min stake allowed, tokens min_stake: 1.0 # max stake allowed, tokens @@ -116,3 +116,8 @@ integration_tests: # lock_interval used by nodes to request server to connect to blockchain # after start lock_interval: 1s + +pre_encryption: + auto_generate: false + mnemonic: 'inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown' + diff --git a/docker.local/Dockerfile.dev b/docker.local/Dockerfile.dev new file mode 100644 index 000000000..a2c38d790 --- /dev/null +++ b/docker.local/Dockerfile.dev @@ -0,0 +1,48 @@ +FROM golang:1.14.9-alpine3.12 as blobber_build + +RUN apk add --update --no-cache build-base linux-headers git cmake bash perl grep + +# Install Herumi's cryptography +RUN apk add gmp gmp-dev openssl-dev && \ + cd /tmp && \ + wget -O - https://github.com/herumi/mcl/archive/master.tar.gz | tar xz && \ + mv mcl* mcl && \ + make -C mcl -j $(nproc) lib/libmclbn256.so install && \ + cp mcl/lib/libmclbn256.so /usr/local/lib && \ + rm -R /tmp/mcl +#TODO: create shared image and remove code duplicates! +RUN git clone https://github.com/herumi/bls /tmp/bls && \ + cd /tmp/bls && \ + git submodule init && \ + git submodule update && \ + make -j $(nproc) install && \ + cd - && \ + rm -R /tmp/bls + +ENV SRC_DIR=/blobber +ENV GO111MODULE=on + +# Download the dependencies: +# Will be cached if we don't change mod/sum files +ADD ./gosdk /gosdk +COPY ./blobber/code/go/0chain.net/go.mod ./blobber/code/go/0chain.net/go.sum $SRC_DIR/go/0chain.net/ +RUN cd $SRC_DIR/go/0chain.net && go mod download + +#Add the source code +ADD ./blobber/code/go/0chain.net $SRC_DIR/go/0chain.net + +WORKDIR $SRC_DIR/go/0chain.net/blobber + +ARG GIT_COMMIT +ENV GIT_COMMIT=$GIT_COMMIT +RUN go build -v -tags "bn256 development" -ldflags "-X 0chain.net/core/build.BuildTag=$GIT_COMMIT" + +# Copy the build artifact into a minimal runtime image: +FROM golang:1.14.9-alpine3.12 +RUN apk add gmp gmp-dev openssl-dev +COPY --from=blobber_build /usr/local/lib/libmcl*.so \ + /usr/local/lib/libbls*.so \ + /usr/local/lib/ +ENV APP_DIR=/blobber +WORKDIR $APP_DIR +COPY --from=blobber_build $APP_DIR/go/0chain.net/blobber/blobber $APP_DIR/bin/blobber diff --git a/docker.local/ValidatorDockerfile.dev b/docker.local/ValidatorDockerfile.dev new file mode 100644 index 000000000..1841a035f --- /dev/null +++ b/docker.local/ValidatorDockerfile.dev @@ -0,0 +1,46 @@ +FROM golang:1.14.9-alpine3.12 as validator_build + +RUN apk add --update --no-cache build-base linux-headers git cmake bash perl grep + +# Install Herumi's cryptography +RUN apk add gmp gmp-dev openssl-dev && \ + cd /tmp && \ + wget -O - https://github.com/herumi/mcl/archive/master.tar.gz | tar xz && \ + mv mcl* mcl && \ + make -C mcl -j $(nproc) lib/libmclbn256.so install && \ + cp mcl/lib/libmclbn256.so /usr/local/lib && \ + rm -R /tmp/mcl + +RUN git clone https://github.com/herumi/bls /tmp/bls && \ + cd /tmp/bls && \ + git submodule init && \ + git submodule update && \ + make -j $(nproc) install && \ + cd - && \ + rm -R /tmp/bls + +ENV SRC_DIR=/blobber +ENV GO111MODULE=on + +# Download the dependencies: +# Will be cached if we don't change mod/sum files +ADD ./gosdk /gosdk +COPY ./blobber/code/go/0chain.net/go.mod ./blobber/code/go/0chain.net/go.sum $SRC_DIR/go/0chain.net/ +RUN cd $SRC_DIR/go/0chain.net && go mod download + +#Add the source code +ADD ./blobber/code/go/0chain.net $SRC_DIR/go/0chain.net + +WORKDIR $SRC_DIR/go/0chain.net/validator + +RUN go build -v -tags "bn256 development" -ldflags "-X 0chain.net/core/build.BuildTag=$GIT_COMMIT" + +# Copy the build artifact into a minimal runtime image: +FROM golang:1.11.4-alpine3.8 +RUN apk add gmp gmp-dev openssl-dev +COPY --from=validator_build /usr/local/lib/libmcl*.so \ + /usr/local/lib/libbls*.so \ + /usr/local/lib/ +ENV APP_DIR=/blobber +WORKDIR $APP_DIR +COPY --from=validator_build $APP_DIR/go/0chain.net/validator/validator $APP_DIR/bin/validator diff --git a/docker.local/bin/build.blobber.dev.sh b/docker.local/bin/build.blobber.dev.sh new file mode 100755 index 000000000..2496338e0 --- /dev/null +++ b/docker.local/bin/build.blobber.dev.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +GIT_COMMIT=$(git rev-list -1 HEAD) +echo $GIT_COMMIT + +docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/ValidatorDockerfile.dev .. -t validator +docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/Dockerfile.dev .. -t blobber + +for i in $(seq 1 6); +do + BLOBBER=$i docker-compose -p blobber$i -f docker.local/docker-compose.yml build --force-rm +done + +docker.local/bin/sync_clock.sh From 19929350f898fc0292ae2c3e65def32afc0d69c7 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 20:06:18 +0545 Subject: [PATCH 18/66] Updates --- code/go/0chain.net/blobber/main.go | 7 ++++--- code/go/0chain.net/blobbercore/config/config.go | 2 +- code/go/0chain.net/blobbercore/reference/marketplace.go | 6 ------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/code/go/0chain.net/blobber/main.go b/code/go/0chain.net/blobber/main.go index 5133f73d0..f54224655 100644 --- a/code/go/0chain.net/blobber/main.go +++ b/code/go/0chain.net/blobber/main.go @@ -96,10 +96,11 @@ func SetupWorkerConfig() { config.Configuration.MinLockDemand = viper.GetFloat64("min_lock_demand") config.Configuration.MaxOfferDuration = viper.GetDuration("max_offer_duration") config.Configuration.ChallengeCompletionTime = viper.GetDuration("challenge_completion_time") - config.Configuration.PreEncryption.AutoGenerate = viper.GetBool("pre_encryption.autogenerate") + config.Configuration.PreEncryption.AutoGenerate = viper.GetBool("pre_encryption.auto_generate") config.Configuration.PreEncryption.Mnemonic = viper.GetString("pre_encryption.mnemonic") - Logger.Panic("pre encryption config", zap.Bool("autogen", config.Configuration.PreEncryption.AutoGenerate)) - Logger.Panic("pre encryption mnemonic", zap.String("mnemonic", config.Configuration.PreEncryption.Mnemonic)) + if !config.Configuration.PreEncryption.AutoGenerate && len(config.Configuration.PreEncryption.Mnemonic) == 0 { + log.Fatal("mnemonic missing for pre_encryption for auto_generate: false") + } config.Configuration.ReadLockTimeout = int64( viper.GetDuration("read_lock_timeout") / time.Second, diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index d147f6684..eb678366e 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -65,7 +65,7 @@ const ( ) type PreEncryptionConfig struct { - AutoGenerate bool `json:"autogenerate,omitempty"` + AutoGenerate bool `json:"auto_generate,omitempty"` Mnemonic string `json:"mnemonic,omitempty"` } diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go index a8b35c82f..55720cbaf 100644 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ b/code/go/0chain.net/blobbercore/reference/marketplace.go @@ -2,12 +2,9 @@ package reference import ( "0chain.net/blobbercore/datastore" - "0chain.net/core/config" - . "0chain.net/core/logging" "context" "github.com/0chain/gosdk/core/zcncrypto" zboxenc "github.com/0chain/gosdk/zboxcore/encryption" - "go.uber.org/zap" ) type MarketplaceInfo struct { @@ -62,7 +59,6 @@ func GetMarketplaceInfoFromMnemonic(mnemonic string) *MarketplaceInfo { } func GetSecretKeyPair() (*KeyPairInfo, error) { - //sigScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) wallet, err := zcncrypto.NewSignatureScheme("ed25519").GenerateKeys() if err != nil { return nil, err @@ -80,9 +76,7 @@ func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { return &row, err } - Logger.Info("Creating key pair", zap.String("signature_scheme", config.Configuration.SignatureScheme)) keyPairInfo, err := GetSecretKeyPair() - Logger.Info("Secret key pair created") if err != nil { return nil, err From 234cf81800895e9e660939d918fc5d3c67b52882 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 20:24:05 +0545 Subject: [PATCH 19/66] Add test for same key for all blobbers --- .../blobbercore/handler/handler_test.go | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 8f1330a29..dfe40cc44 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -43,6 +43,7 @@ func init() { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) + bconfig.Configuration.PreEncryption.AutoGenerate = true } func setup(t *testing.T) { @@ -245,6 +246,34 @@ func TestMarketplaceApi(t *testing.T) { assert.Equal(t, wantBody, recorder.Body.String()) }) + t.Run("marketplace_key_existing_same_for_all_blobbers_read_from_config", func(t *testing.T) { + datastore.MockTheStore(t) + bconfig.Configuration.PreEncryption.AutoGenerate = false + bconfig.Configuration.PreEncryption.Mnemonic = + "inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown" + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/secret"] + + url, err := router.Get(handlerName).URL() + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + wantBody := `{"mnemonic":"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown"}` + "\n" + assert.Equal(t, wantBody, recorder.Body.String()) + }) + t.Run("marketplace_create_new_key_and_return", func(t *testing.T) { mock := datastore.MockTheStore(t) setupDbMock := func(mock sqlmock.Sqlmock) { @@ -256,7 +285,7 @@ func TestMarketplaceApi(t *testing.T) { ) mock.ExpectExec(`INSERT INTO "marketplace"`). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg()). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). WillReturnResult(sqlmock.NewResult(0, 0)) From b7eb155fe5cf416608dc2ca6c2f04e9f4c6635ce Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 24 May 2021 21:35:14 +0545 Subject: [PATCH 20/66] Updates --- README.md | 9 +- .../blobbercore/handler/handler_test.go | 128 ----------- .../blobbercore/handler/marketplace_test.go | 216 ++++++++++++++++++ .../blobbercore/handler/storage_handler.go | 2 +- 4 files changed, 225 insertions(+), 130 deletions(-) create mode 100644 code/go/0chain.net/blobbercore/handler/marketplace_test.go diff --git a/README.md b/README.md index 08f70ce00..c1630c531 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,14 @@ $ docker network create --driver=bridge --subnet=198.18.0.0/15 --gateway=198.18. $ ./docker.local/bin/build.blobber.sh ``` - + +To link to local gosdk so that the changes are reflected on the blobber build please use the below command + +``` + +$ ./docker.local/bin/build.blobber.dev.sh + +``` 3. After building the container for blobber, go to Blobber1 directory (git/blobber/docker.local/blobber1) and run the container using diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index dfe40cc44..f3e5dfc07 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -14,7 +14,6 @@ import ( "bytes" "encoding/json" "errors" - "fmt" "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zcncore" "github.com/DATA-DOG/go-sqlmock" @@ -172,15 +171,6 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(uName) - marketplacePath := "/v1/marketplace/secret" - mName := "MarketplaceInfo" - router.HandleFunc(marketplacePath, common.UserRateLimit( - common.ToJSONResponse( - WithReadOnlyConnection(MarketPlaceSecretHandler), - ), - ), - ).Name(mName) - return router, map[string]string{ opPath: opName, @@ -192,7 +182,6 @@ func setupHandlers() (*mux.Router, map[string]string) { cPath: cName, aPath: aName, uPath: uName, - marketplacePath: mName, } } @@ -205,123 +194,6 @@ func isEndpointAllowGetReq(name string) bool { } } -func TestMarketplaceApi(t *testing.T) { - setup(t) - router, handlers := setupHandlers() - - t.Run("marketplace_key_existing", func(t *testing.T) { - mock := datastore.MockTheStore(t) - setupDbMock := func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). - WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}). - AddRow("pub", "prv", "a b c d"), - ) - - mock.ExpectCommit() - } - setupDbMock(mock) - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - wantBody := `{"mnemonic":"a b c d"}` + "\n" - assert.Equal(t, wantBody, recorder.Body.String()) - }) - - t.Run("marketplace_key_existing_same_for_all_blobbers_read_from_config", func(t *testing.T) { - datastore.MockTheStore(t) - bconfig.Configuration.PreEncryption.AutoGenerate = false - bconfig.Configuration.PreEncryption.Mnemonic = - "inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown" - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - wantBody := `{"mnemonic":"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown"}` + "\n" - assert.Equal(t, wantBody, recorder.Body.String()) - }) - - t.Run("marketplace_create_new_key_and_return", func(t *testing.T) { - mock := datastore.MockTheStore(t) - setupDbMock := func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). - WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}), - ) - - mock.ExpectExec(`INSERT INTO "marketplace"`). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(0, 0)) - - - mock.ExpectCommit() - } - setupDbMock(mock) - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - marketplaceInfo := reference.MarketplaceInfo {} - json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) - assert.NotEmpty(t, marketplaceInfo) - assert.Empty(t, marketplaceInfo.PublicKey) - assert.Empty(t, marketplaceInfo.PrivateKey) - assert.NotEmpty(t, marketplaceInfo.Mnemonic) - fmt.Println(marketplaceInfo) - }) - -} - func TestHandlers_Requiring_Signature(t *testing.T) { setup(t) diff --git a/code/go/0chain.net/blobbercore/handler/marketplace_test.go b/code/go/0chain.net/blobbercore/handler/marketplace_test.go new file mode 100644 index 000000000..1bae7912f --- /dev/null +++ b/code/go/0chain.net/blobbercore/handler/marketplace_test.go @@ -0,0 +1,216 @@ +package handler + +import ( + bconfig "0chain.net/blobbercore/config" + "0chain.net/blobbercore/datastore" + "0chain.net/blobbercore/filestore" + "0chain.net/blobbercore/reference" + "0chain.net/core/chain" + "0chain.net/core/common" + "0chain.net/core/config" + "0chain.net/core/logging" + "encoding/json" + "fmt" + "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zcncore" + "github.com/DATA-DOG/go-sqlmock" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "net/http" + "net/http/httptest" + "os" + "regexp" + "testing" +) + +func init() { + common.ConfigRateLimits() + chain.SetServerChain(&chain.Chain{}) + config.Configuration.SignatureScheme = "bls0chain" + logging.Logger = zap.NewNop() + + dir, _ := os.Getwd() + if _, err := filestore.SetupFSStore(dir + "/tmp"); err != nil { + panic(err) + } + bconfig.Configuration.MaxFileSize = int64(1 << 30) + bconfig.Configuration.PreEncryption.AutoGenerate = true +} + +func setupTest(t *testing.T) { + // setup wallet + w, err := zcncrypto.NewBLS0ChainScheme().GenerateKeys() + if err != nil { + t.Fatal(err) + } + wBlob, err := json.Marshal(w) + if err != nil { + t.Fatal(err) + } + if err := zcncore.SetWalletInfo(string(wBlob), true); err != nil { + t.Fatal(err) + } + + // setup servers + sharderServ := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + }, + ), + ) + server := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + n := zcncore.Network{Miners: []string{"miner 1"}, Sharders: []string{sharderServ.URL}} + blob, err := json.Marshal(n) + if err != nil { + t.Fatal(err) + } + + if _, err := w.Write(blob); err != nil { + t.Fatal(err) + } + }, + ), + ) + + if err := zcncore.InitZCNSDK(server.URL, "ed25519"); err != nil { + t.Fatal(err) + } +} + +func setupHandler() (*mux.Router, map[string]string) { + router := mux.NewRouter() + + marketplacePath := "/v1/marketplace/secret" + mName := "MarketplaceInfo" + router.HandleFunc(marketplacePath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(MarketPlaceSecretHandler), + ), + ), + ).Name(mName) + + return router, + map[string]string{ + marketplacePath: mName, + } +} + +func TestMarketplaceApi(t *testing.T) { + setupTest(t) + router, handlers := setupHandler() + + t.Run("marketplace_key_existing", func(t *testing.T) { + mock := datastore.MockTheStore(t) + setupDbMock := func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). + WillReturnRows( + sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}). + AddRow("pub", "prv", "a b c d"), + ) + + mock.ExpectCommit() + } + setupDbMock(mock) + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/secret"] + + url, err := router.Get(handlerName).URL() + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + wantBody := `{"mnemonic":"a b c d"}` + "\n" + assert.Equal(t, wantBody, recorder.Body.String()) + }) + + t.Run("marketplace_key_existing_same_for_all_blobbers_read_from_config", func(t *testing.T) { + datastore.MockTheStore(t) + bconfig.Configuration.PreEncryption.AutoGenerate = false + bconfig.Configuration.PreEncryption.Mnemonic = + "inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown" + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/secret"] + + url, err := router.Get(handlerName).URL() + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + wantBody := `{"mnemonic":"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown"}` + "\n" + assert.Equal(t, wantBody, recorder.Body.String()) + }) + + t.Run("marketplace_create_new_key_and_return", func(t *testing.T) { + mock := datastore.MockTheStore(t) + setupDbMock := func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). + WillReturnRows( + sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}), + ) + + mock.ExpectExec(`INSERT INTO "marketplace"`). + WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(0, 0)) + + + mock.ExpectCommit() + } + setupDbMock(mock) + httprequest := func() *http.Request { + handlerName := handlers["/v1/marketplace/secret"] + + url, err := router.Get(handlerName).URL() + if err != nil { + t.Fatal() + } + + r, err := http.NewRequest(http.MethodGet, url.String(), nil) + if err != nil { + t.Fatal(err) + } + + return r + }() + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httprequest) + assert.Equal(t, 200, 200) + marketplaceInfo := reference.MarketplaceInfo {} + json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) + assert.NotEmpty(t, marketplaceInfo) + assert.Empty(t, marketplaceInfo.PublicKey) + assert.Empty(t, marketplaceInfo.PrivateKey) + assert.NotEmpty(t, marketplaceInfo.Mnemonic) + fmt.Println(marketplaceInfo) + }) + +} diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index 30f1db7a1..6c15d55c1 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -711,7 +711,7 @@ func verifySignatureFromRequest(r *http.Request, pbK string) (bool, error) { return false, common.NewError("invalid_params", "Missing allocation tx") } - return encryption.Verify(pbK, sign, data) + return encryption.Verify(pbK, sign, encryption.Hash(data)) } // pathsFromReq retrieves paths value from request which can be represented as single "path" value or "paths" values, From 50b452e482b45297bca9010419cfd1c94a9daee2 Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 27 May 2021 18:34:57 +0545 Subject: [PATCH 21/66] Add 0box config for getting share info --- config/0chain_blobber.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/0chain_blobber.yaml b/config/0chain_blobber.yaml index df126b630..a7949a6ad 100755 --- a/config/0chain_blobber.yaml +++ b/config/0chain_blobber.yaml @@ -42,7 +42,7 @@ max_file_size: 10485760 #10MB update_allocations_interval: 1m # delegate wallet (must be set) -delegate_wallet: '9a566aa4f8e8c342fed97c8928040a21f21b8f574e5782c28568635ba9c75a85' +delegate_wallet: '34d7afcf6b1477d7099845eda897e2d341ad5672ee33f14ddde80eb3f789c58a' # min stake allowed, tokens min_stake: 1.0 # max stake allowed, tokens @@ -53,6 +53,7 @@ num_delegates: 50 service_charge: 0.30 block_worker: http://198.18.0.98:9091 +0box: http://198.18.0.98:9081 handlers: rate_limit: 10 # 10 per second From a69635f551271e26cd68764177f61b2297106bd1 Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 27 May 2021 20:54:23 +0545 Subject: [PATCH 22/66] Reencrypt using regen key at blobber --- .../handler/object_operation_handler.go | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 59e6ddd26..8e8c2f4b6 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -1,10 +1,12 @@ package handler import ( + "bytes" "context" "encoding/hex" "encoding/json" "errors" + "strings" "net/http" "path/filepath" @@ -19,11 +21,11 @@ import ( "0chain.net/blobbercore/reference" "0chain.net/blobbercore/stats" "0chain.net/blobbercore/writemarker" - "0chain.net/core/common" "0chain.net/core/encryption" "0chain.net/core/lock" "0chain.net/core/node" + zencryption "github.com/0chain/gosdk/zboxcore/encryption" "gorm.io/datatypes" "gorm.io/gorm" @@ -280,6 +282,12 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( clientIDForReadRedeem = allocationObj.OwnerID } + var attrs *reference.Attributes + if attrs, err = fileref.GetAttributes(); err != nil { + return nil, common.NewErrorf("download_file", + "error getting file attributes: %v", err) + } + if (allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID && !isACollaborator) || len(authTokenString) > 0 { @@ -304,12 +312,6 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "error parsing the auth ticket for download: %v", err) } - var attrs *reference.Attributes - if attrs, err = fileref.GetAttributes(); err != nil { - return nil, common.NewErrorf("download_file", - "error getting file attributes: %v", err) - } - // if --rx_pay used 3rd_party pays if rxPay { clientIDForReadRedeem = clientID @@ -401,6 +403,40 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( var response = &DownloadResponse{} response.Success = true response.LatestRM = readMarker + if attrs.PreAtBlobber { + buyerPublicKey := "read from 0box" + blobberMnemonic := "read from table" + + var encscheme zencryption.EncryptionScheme + encscheme.Initialize(blobberMnemonic) + encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey) + + encMsg := &zencryption.EncryptedMessage{} + + encMsg.EncryptedData = respData[(2 * 1024):] + + headerBytes := respData[:(2 * 1024)] + headerBytes = bytes.Trim(headerBytes, "\x00") + headerString := string(headerBytes) + + headerChecksums := strings.Split(headerString, ",") + if len(headerChecksums) != 2 { + Logger.Error("Block has invalid header", zap.String("request Url", r.URL.String())) + return nil, errors.New("Block has invalid header for request " + r.URL.String()) + } + + encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] + encMsg.EncryptedKey = encscheme.GetEncryptedKey() + + regenKey, _ := encscheme.GetReGenKey(buyerPublicKey, "filetype:audio") + reEncMsg, _ := encscheme.ReEncrypt(encMsg, regenKey) + + encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] + respData, err = reEncMsg.MarshalJSON() + if err != nil { + return nil, err + } + } response.Data = respData response.Path = fileref.Path response.AllocationID = fileref.AllocationID From 4e56f664095012f334f07f81d0f2b0219e91beff Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 27 May 2021 21:02:06 +0545 Subject: [PATCH 23/66] Read mnemonic from table --- .../blobbercore/handler/object_operation_handler.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 8e8c2f4b6..d2a8b8f67 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -404,8 +404,13 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.Success = true response.LatestRM = readMarker if attrs.PreAtBlobber { - buyerPublicKey := "read from 0box" - blobberMnemonic := "read from table" + buyerPublicKey := r.FormValue("public_key") + encInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) + blobberMnemonic := encInfo.Mnemonic + + if err != nil { + return nil, err + } var encscheme zencryption.EncryptionScheme encscheme.Initialize(blobberMnemonic) From ca6993da3727daeb16c1273a75d4c99d2374a37a Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 28 May 2021 21:21:43 +0545 Subject: [PATCH 24/66] Fixes from testing --- .../blobbercore/handler/object_operation_handler.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index d2a8b8f67..f7f15cef1 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -404,15 +404,16 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.Success = true response.LatestRM = readMarker if attrs.PreAtBlobber { - buyerPublicKey := r.FormValue("public_key") + // buyerPublicKey := r.FormValue("public_key") + buyerPublicKey := "48a620624d143a2b49fa50ccd5068eb85931d2d3e68ccd365cc380b88003e424dbc30346965dfbcda045a801c40e36aa3df2de6af530768767ba704eb54c7e05" encInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) - blobberMnemonic := encInfo.Mnemonic - if err != nil { return nil, err } - var encscheme zencryption.EncryptionScheme + blobberMnemonic := encInfo.Mnemonic + + encscheme := zencryption.NewEncryptionScheme() encscheme.Initialize(blobberMnemonic) encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey) @@ -436,7 +437,6 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( regenKey, _ := encscheme.GetReGenKey(buyerPublicKey, "filetype:audio") reEncMsg, _ := encscheme.ReEncrypt(encMsg, regenKey) - encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] respData, err = reEncMsg.MarshalJSON() if err != nil { return nil, err From 577cb92fdcf0e0fbd0989b3eb13a1c759c0699bc Mon Sep 17 00:00:00 2001 From: Uk Date: Sun, 30 May 2021 18:55:05 +0545 Subject: [PATCH 25/66] Add detailed error capture and logging --- .../blobbercore/handler/object_operation_handler.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index f7f15cef1..15c2e1973 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -434,8 +434,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] encMsg.EncryptedKey = encscheme.GetEncryptedKey() - regenKey, _ := encscheme.GetReGenKey(buyerPublicKey, "filetype:audio") - reEncMsg, _ := encscheme.ReEncrypt(encMsg, regenKey) + regenKey, err := encscheme.GetReGenKey(buyerEncryptionPublicKey, "filetype:audio") + if err != nil { + return nil, err + } + reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey) + if err != nil { + return nil, err + } respData, err = reEncMsg.MarshalJSON() if err != nil { From 399dcf6c0a38f6c80f248f00ae19096440a23609 Mon Sep 17 00:00:00 2001 From: Uk Date: Sun, 30 May 2021 22:16:15 +0545 Subject: [PATCH 26/66] Blobber updates --- .../0chain.net/blobbercore/handler/object_operation_handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 15c2e1973..119fcabd2 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -438,12 +438,13 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( if err != nil { return nil, err } - reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey) + reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey, buyerEncryptionPublicKey) if err != nil { return nil, err } respData, err = reEncMsg.MarshalJSON() + respData = append([]byte("PRE_AT_BLOBBER"), respData...) if err != nil { return nil, err } From 41f4fe678321c96d885eac9f64049dabbe8960e2 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 31 May 2021 17:16:43 +0545 Subject: [PATCH 27/66] Fix marshall --- .../0chain.net/blobbercore/handler/object_operation_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 119fcabd2..4da72699f 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -443,7 +443,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( return nil, err } - respData, err = reEncMsg.MarshalJSON() + respData, err = reEncMsg.Marshal() respData = append([]byte("PRE_AT_BLOBBER"), respData...) if err != nil { return nil, err From ba2491945e75d75f8b21ecf06544629fd8922cd3 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 31 May 2021 17:54:09 +0545 Subject: [PATCH 28/66] Add chuked transfer of reencrypted data --- .../handler/object_operation_handler.go | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 4da72699f..f3bd63c71 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -405,7 +405,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.LatestRM = readMarker if attrs.PreAtBlobber { // buyerPublicKey := r.FormValue("public_key") - buyerPublicKey := "48a620624d143a2b49fa50ccd5068eb85931d2d3e68ccd365cc380b88003e424dbc30346965dfbcda045a801c40e36aa3df2de6af530768767ba704eb54c7e05" + buyerEncryptionPublicKey := "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio=" encInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) if err != nil { return nil, err @@ -417,37 +417,42 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( encscheme.Initialize(blobberMnemonic) encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey) - encMsg := &zencryption.EncryptedMessage{} + totalSize := len(respData) + result := []byte {} + for i := 0; i < totalSize; i += reference.CHUNK_SIZE - 16 { + encMsg := &zencryption.EncryptedMessage{} - encMsg.EncryptedData = respData[(2 * 1024):] + encMsg.EncryptedData = respData[(2 * 1024):] - headerBytes := respData[:(2 * 1024)] - headerBytes = bytes.Trim(headerBytes, "\x00") - headerString := string(headerBytes) + headerBytes := respData[:(2 * 1024)] + headerBytes = bytes.Trim(headerBytes, "\x00") + headerString := string(headerBytes) - headerChecksums := strings.Split(headerString, ",") - if len(headerChecksums) != 2 { - Logger.Error("Block has invalid header", zap.String("request Url", r.URL.String())) - return nil, errors.New("Block has invalid header for request " + r.URL.String()) - } + headerChecksums := strings.Split(headerString, ",") + if len(headerChecksums) != 2 { + Logger.Error("Block has invalid header", zap.String("request Url", r.URL.String())) + return nil, errors.New("Block has invalid header for request " + r.URL.String()) + } - encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] - encMsg.EncryptedKey = encscheme.GetEncryptedKey() + encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] + encMsg.EncryptedKey = encscheme.GetEncryptedKey() - regenKey, err := encscheme.GetReGenKey(buyerEncryptionPublicKey, "filetype:audio") - if err != nil { - return nil, err - } - reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey, buyerEncryptionPublicKey) - if err != nil { - return nil, err - } + regenKey, err := encscheme.GetReGenKey(buyerEncryptionPublicKey, "filetype:audio") + if err != nil { + return nil, err + } + reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey, buyerEncryptionPublicKey) + if err != nil { + return nil, err + } - respData, err = reEncMsg.Marshal() - respData = append([]byte("PRE_AT_BLOBBER"), respData...) - if err != nil { - return nil, err + encData, err := reEncMsg.Marshal() + if err != nil { + return nil, err + } + result = append(result, encData...) } + respData = result } response.Data = respData response.Path = fileref.Path From 4b43806061c20169c17ecdb033df1ca61ad62411 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 31 May 2021 18:28:04 +0545 Subject: [PATCH 29/66] Update download chunking --- .../blobbercore/handler/object_operation_handler.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index f3bd63c71..cc06a06e9 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "math" "strings" "net/http" @@ -419,12 +420,13 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( totalSize := len(respData) result := []byte {} - for i := 0; i < totalSize; i += reference.CHUNK_SIZE - 16 { + for i := 0; i < totalSize; i += reference.CHUNK_SIZE { encMsg := &zencryption.EncryptedMessage{} + chunkData := respData[i : int64(math.Min(float64(i + reference.CHUNK_SIZE), float64(totalSize)))] - encMsg.EncryptedData = respData[(2 * 1024):] + encMsg.EncryptedData = chunkData[(2 * 1024):] - headerBytes := respData[:(2 * 1024)] + headerBytes := chunkData[:(2 * 1024)] headerBytes = bytes.Trim(headerBytes, "\x00") headerString := string(headerBytes) From 16fbbeb23114304455f6925e1f131ab3a9241be3 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 1 Jun 2021 22:04:12 +0545 Subject: [PATCH 30/66] Add api to insert auth ticket --- .../handler/blobber_meta_handler.go | 22 ----- .../0chain.net/blobbercore/handler/handler.go | 50 ++++++++-- .../handler/object_operation_handler.go | 22 ++--- .../blobbercore/reference/marketplace.go | 92 ------------------- .../blobbercore/reference/shareinfo.go | 56 +++++++++++ ...{marketplace_test.go => shareinfo_test.go} | 0 code/go/0chain.net/core/common/time.go | 6 ++ config/0chain_blobber.yaml | 1 - sql/14-add-marketplace-table.sql | 20 +++- 9 files changed, 132 insertions(+), 137 deletions(-) delete mode 100644 code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go delete mode 100644 code/go/0chain.net/blobbercore/reference/marketplace.go create mode 100644 code/go/0chain.net/blobbercore/reference/shareinfo.go rename code/go/0chain.net/blobbercore/reference/{marketplace_test.go => shareinfo_test.go} (100%) diff --git a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go b/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go deleted file mode 100644 index e3d7ea96e..000000000 --- a/code/go/0chain.net/blobbercore/handler/blobber_meta_handler.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build !integration_tests - -package handler - -import ( - "0chain.net/blobbercore/config" - "context" - "net/http" - - "0chain.net/blobbercore/reference" -) - - -func GetOrCreateMarketplaceEncryptionKeyPair(ctx context.Context, r *http.Request) (*reference.MarketplaceInfo, error) { - if !config.Configuration.PreEncryption.AutoGenerate { - mnemonic := config.Configuration.PreEncryption.Mnemonic - return reference.GetMarketplaceInfoFromMnemonic(mnemonic), nil - } - info, err := reference.GetOrCreateMarketplaceInfo(ctx) - - return info, err -} diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 3fa95a218..fd77232b6 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -3,7 +3,11 @@ package handler import ( + "0chain.net/blobbercore/readmarker" + "0chain.net/blobbercore/reference" "context" + "encoding/json" + "errors" "net/http" "os" "runtime/pprof" @@ -59,7 +63,7 @@ func SetupHandlers(r *mux.Router) { r.HandleFunc("/getstats", common.UserRateLimit(common.ToJSONResponse(stats.GetStatsHandler))) //marketplace related - r.HandleFunc("/v1/marketplace/secret", common.UserRateLimit(common.ToJSONResponse(WithConnection(MarketPlaceSecretHandler)))) + r.HandleFunc("/v1/marketplace/shareinfo/{allocation}", common.UserRateLimit(common.ToJSONResponse(WithConnection(MarketPlaceShareInfoHandler)))) } func WithReadOnlyConnection(handler common.JSONResponderF) common.JSONResponderF { @@ -320,14 +324,48 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro return "cleanup", err } -func MarketPlaceSecretHandler(ctx context.Context, r *http.Request) (interface{}, error) { - marketplaceInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) +func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interface{}, error) { + allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) + allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) + if err != nil { + return nil, common.NewError("invalid_parameters", "Invalid allocation id passed." + err.Error()) + } + + if r.Method != "POST" { + return nil, errors.New("invalid request method, only POST is allowed") + } + + encryptionPublicKey := r.FormValue("encryption_public_key") + authTicketString := r.FormValue("auth_ticket") + authTicket := &readmarker.AuthTicket{} + + err = json.Unmarshal([]byte(authTicketString), &authTicket) + if err != nil { + return false, common.NewError("invalid_parameters", "Error parsing the auth ticket for download." + err.Error()) + } + + fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, authTicket.FilePathHash) + if err != nil { + return nil, common.NewError("invalid_parameters", "Invalid file path. " + err.Error()) + } + + authTicketVerified, err := storageHandler.verifyAuthTicket(ctx, authTicketString, allocationObj, fileref, authTicket.ClientID) + if !authTicketVerified { + return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.") + } + if err != nil { return nil, err } - return map[string]string { - "mnemonic": marketplaceInfo.Mnemonic, - }, nil + shareInfo := reference.ShareInfo{ + OwnerID: authTicket.OwnerID, + ClientID: authTicket.ClientID, + FileName: authTicket.FileName, + ReEncryptionKey: authTicket.ReEncryptionKey, + ClientEncryptionPublicKey: encryptionPublicKey, + ExpiryAt: common.ToTime(authTicket.Expiration), + } + reference.AddShareInfo(ctx, shareInfo) } diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index cc06a06e9..f112bcba6 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -405,18 +405,22 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.Success = true response.LatestRM = readMarker if attrs.PreAtBlobber { - // buyerPublicKey := r.FormValue("public_key") - buyerEncryptionPublicKey := "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio=" - encInfo, err := GetOrCreateMarketplaceEncryptionKeyPair(ctx, r) + // check if client is authorized to download + shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.Path) if err != nil { return nil, err } - blobberMnemonic := encInfo.Mnemonic - + buyerEncryptionPublicKey := shareInfo.ClientEncryptionPublicKey encscheme := zencryption.NewEncryptionScheme() - encscheme.Initialize(blobberMnemonic) + // reEncrypt does not require pub / private key, + // we could probably make it a classless function + + encscheme.Initialize("") encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey) + if err != nil { + return nil, err + } totalSize := len(respData) result := []byte {} @@ -439,11 +443,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( encMsg.MessageChecksum, encMsg.OverallChecksum = headerChecksums[0], headerChecksums[1] encMsg.EncryptedKey = encscheme.GetEncryptedKey() - regenKey, err := encscheme.GetReGenKey(buyerEncryptionPublicKey, "filetype:audio") - if err != nil { - return nil, err - } - reEncMsg, err := encscheme.ReEncrypt(encMsg, regenKey, buyerEncryptionPublicKey) + reEncMsg, err := encscheme.ReEncrypt(encMsg, shareInfo.ReEncryptionKey, buyerEncryptionPublicKey) if err != nil { return nil, err } diff --git a/code/go/0chain.net/blobbercore/reference/marketplace.go b/code/go/0chain.net/blobbercore/reference/marketplace.go deleted file mode 100644 index 55720cbaf..000000000 --- a/code/go/0chain.net/blobbercore/reference/marketplace.go +++ /dev/null @@ -1,92 +0,0 @@ -package reference - -import ( - "0chain.net/blobbercore/datastore" - "context" - "github.com/0chain/gosdk/core/zcncrypto" - zboxenc "github.com/0chain/gosdk/zboxcore/encryption" -) - -type MarketplaceInfo struct { - Mnemonic string `gorm:"mnemonic" json:"mnemonic,omitempty"` - PublicKey string `gorm:"public_key" json:"public_key"` - PrivateKey string `gorm:"private_key" json:"private_key,omitempty"` -} - -type KeyPairInfo struct { - PublicKey string - PrivateKey string - Mnemonic string -} - -func TableName() string { - return "marketplace" -} - -func AddEncryptionKeyPairInfo(ctx context.Context, keyPairInfo KeyPairInfo) error { - db := datastore.GetStore().GetTransaction(ctx) - return db.Table(TableName()).Create(&MarketplaceInfo{ - PrivateKey: keyPairInfo.PrivateKey, - PublicKey: keyPairInfo.PublicKey, - Mnemonic: keyPairInfo.Mnemonic, - }).Error -} - -func GetMarketplaceInfo(ctx context.Context) (MarketplaceInfo, error) { - db := datastore.GetStore().GetTransaction(ctx) - marketplaceInfo := MarketplaceInfo{} - err := db.Table(TableName()).First(&marketplaceInfo).Error - return marketplaceInfo, err -} - -func GetSignatureScheme() zcncrypto.SignatureScheme { - // TODO: bls0chain scheme crashes - return zcncrypto.NewSignatureScheme("ed25519") -} - -func GetMarketplaceInfoFromMnemonic(mnemonic string) *MarketplaceInfo { - encscheme := zboxenc.NewEncryptionScheme() - encscheme.Initialize(mnemonic) - - PrivateKey, _ := encscheme.GetPrivateKey() - PublicKey, _ := encscheme.GetPublicKey() - - return &MarketplaceInfo{ - PrivateKey: PrivateKey, - PublicKey: PublicKey, - Mnemonic: mnemonic, - } -} - -func GetSecretKeyPair() (*KeyPairInfo, error) { - wallet, err := zcncrypto.NewSignatureScheme("ed25519").GenerateKeys() - if err != nil { - return nil, err - } - return &KeyPairInfo { - PublicKey: wallet.Keys[0].PublicKey, - PrivateKey: wallet.Keys[0].PrivateKey, - Mnemonic: wallet.Mnemonic, - }, nil -} - -func GetOrCreateMarketplaceInfo(ctx context.Context) (*MarketplaceInfo, error) { - row, err := GetMarketplaceInfo(ctx) - if err == nil { - return &row, err - } - - keyPairInfo, err := GetSecretKeyPair() - - if err != nil { - return nil, err - } - - AddEncryptionKeyPairInfo(ctx, *keyPairInfo) - - return &MarketplaceInfo{ - PrivateKey: keyPairInfo.PrivateKey, - PublicKey: keyPairInfo.PublicKey, - Mnemonic: keyPairInfo.Mnemonic, - }, nil -} diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go new file mode 100644 index 000000000..15c68de4b --- /dev/null +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -0,0 +1,56 @@ +package reference + +import ( + "0chain.net/blobbercore/datastore" + "context" + "github.com/0chain/gosdk/core/zcncrypto" + zboxenc "github.com/0chain/gosdk/zboxcore/encryption" + "time" +) + + +type ShareInfo struct { + OwnerID string `gorm:"owner_id" json:"owner_id,omitempty"` + ClientID string `gorm:"client_id" json:"client_id"` + FileName string `gorm:"file_name" json:"file_name,omitempty"` + ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` + ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` + ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` +} + +func TableName() string { + return "marketplace_share_info" +} + +func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { + db := datastore.GetStore().GetTransaction(ctx) + return db.Table(TableName()).Create(shareInfo).Error +} + +func GetShareInfo(ctx context.Context, clientID string, fileName string) (ShareInfo, error) { + db := datastore.GetStore().GetTransaction(ctx) + shareInfo := ShareInfo{} + err := db.Table(TableName()). + Where(&ShareInfo{ + ClientID: clientID, + FileName: fileName, + }). + First(&shareInfo).Error + + return shareInfo, err +} + +func GetSignatureScheme() zcncrypto.SignatureScheme { + // TODO: bls0chain scheme crashes + return zcncrypto.NewSignatureScheme("ed25519") +} + +func CreateRegenKey(clientEncryptionPublicKey string, tag string) (string, error) { + encscheme := GetSignatureScheme() + key, err := encscheme.GetReGenKey(clientEncryptionPublicKey, tag) + if err != nil { + return "", err + } + return key, nil +} + diff --git a/code/go/0chain.net/blobbercore/reference/marketplace_test.go b/code/go/0chain.net/blobbercore/reference/shareinfo_test.go similarity index 100% rename from code/go/0chain.net/blobbercore/reference/marketplace_test.go rename to code/go/0chain.net/blobbercore/reference/shareinfo_test.go diff --git a/code/go/0chain.net/core/common/time.go b/code/go/0chain.net/core/common/time.go index d5ede55ba..47da3f17c 100644 --- a/code/go/0chain.net/core/common/time.go +++ b/code/go/0chain.net/core/common/time.go @@ -17,3 +17,9 @@ func Within(ts int64, seconds int64) bool { now := time.Now().Unix() return now > ts-seconds && now < ts+seconds } + +//ToTime - converts the common.Timestamp to time.Time +func ToTime(ts Timestamp) time.Time { + return time.Unix(int64(ts), 0) +} + diff --git a/config/0chain_blobber.yaml b/config/0chain_blobber.yaml index a7949a6ad..55e54230d 100755 --- a/config/0chain_blobber.yaml +++ b/config/0chain_blobber.yaml @@ -53,7 +53,6 @@ num_delegates: 50 service_charge: 0.30 block_worker: http://198.18.0.98:9091 -0box: http://198.18.0.98:9081 handlers: rate_limit: 10 # 10 per second diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index 68e350ed4..b81f89fb3 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -1,10 +1,20 @@ \connect blobber_meta; -CREATE TABLE marketplace ( - private_key VARCHAR(512) NOT NULL, - public_key VARCHAR(512) NOT NULL, - mnemonic VARCHAR(512) NOT NULL +CREATE TABLE marketplace_share_info ( + id BIGSERIAL PRIMARY KEY, + owner_id VARCHAR(64) NOT NULL, + client_id VARCHAR(64) NOT NULL, + file_name TEXT NOT NULL, + re_encryption_key TEXT NOT NULL, + expiry_at TIMESTAMP NULL, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); -GRANT ALL PRIVILEGES ON TABLE marketplace TO blobber_user; +CREATE INDEX idx_marketplace_share_info_for_owner ON marketplace_share_info(owner_id, file_name); +CREATE INDEX idx_marketplace_share_info_for_client ON marketplace_share_info(client_id, file_name); + +CREATE TRIGGER share_info_modtime BEFORE UPDATE ON marketplace_share_info FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); + +GRANT ALL PRIVILEGES ON TABLE marketplace_share_info TO blobber_user; From a98de97a91a0eaa9420cd664f137bf721fffeb01 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 2 Jun 2021 21:28:50 +0545 Subject: [PATCH 31/66] Add tests --- .../0chain.net/blobbercore/handler/handler.go | 24 +- .../blobbercore/handler/handler_test.go | 142 +++++++++++- .../blobbercore/handler/marketplace_test.go | 216 ------------------ .../blobbercore/reference/shareinfo.go | 19 +- 4 files changed, 161 insertions(+), 240 deletions(-) delete mode 100644 code/go/0chain.net/blobbercore/handler/marketplace_test.go diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index fd77232b6..9319396fc 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -325,14 +325,20 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro } func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interface{}, error) { + if r.Method != "POST" { + return nil, errors.New("invalid request method, only POST is allowed") + } + + ctx = setupHandlerContext(ctx, r) allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed." + err.Error()) } - if r.Method != "POST" { - return nil, errors.New("invalid request method, only POST is allowed") + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") } encryptionPublicKey := r.FormValue("encryption_public_key") @@ -351,7 +357,7 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac authTicketVerified, err := storageHandler.verifyAuthTicket(ctx, authTicketString, allocationObj, fileref, authTicket.ClientID) if !authTicketVerified { - return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.") + return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket. " + err.Error()) } if err != nil { @@ -366,6 +372,16 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac ClientEncryptionPublicKey: encryptionPublicKey, ExpiryAt: common.ToTime(authTicket.Expiration), } - reference.AddShareInfo(ctx, shareInfo) + + err = reference.AddShareInfo(ctx, shareInfo) + if err != nil { + return nil, err + } + + resp := map[string]string { + "message": "Share info added successfully", + } + + return resp, nil } diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index f3e5dfc07..9671581af 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -14,7 +14,11 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "github.com/0chain/gosdk/core/zcncrypto" + "github.com/0chain/gosdk/zboxcore/client" + "github.com/0chain/gosdk/zboxcore/fileref" + "github.com/0chain/gosdk/zboxcore/marker" "github.com/0chain/gosdk/zcncore" "github.com/DATA-DOG/go-sqlmock" "github.com/gorilla/mux" @@ -27,6 +31,7 @@ import ( "net/http/httptest" "os" "regexp" + "strings" "testing" "time" ) @@ -171,6 +176,16 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(uName) + sharePath := "/v1/marketplace/shareinfo/{allocation}" + shareName := "Share" + router.HandleFunc(sharePath, common.UserRateLimit( + common.ToJSONResponse( + WithReadOnlyConnection(MarketPlaceShareInfoHandler), + ), + ), + ).Name(shareName) + + return router, map[string]string{ opPath: opName, @@ -182,24 +197,51 @@ func setupHandlers() (*mux.Router, map[string]string) { cPath: cName, aPath: aName, uPath: uName, + sharePath: shareName, } } func isEndpointAllowGetReq(name string) bool { switch name { - case "Stats", "Rename", "Copy", "Attributes", "Upload": + case "Stats", "Rename", "Copy", "Attributes", "Upload", "Share": return false default: return true } } +func GetAuthTicketForEncryptedFile(allocationID string, remotePath string, fileHash string, clientID string, encPublicKey string) (string, error) { + at := &marker.AuthTicket{} + at.AllocationID = allocationID + at.OwnerID = client.GetClientID() + at.ClientID = clientID + at.FileName = remotePath + at.FilePathHash = fileHash + at.RefType = fileref.FILE + timestamp := int64(common.Now()) + at.Expiration = timestamp + 7776000 + at.Timestamp = timestamp + err := at.Sign() + if err != nil { + return "", err + } + atBytes, err := json.Marshal(at) + if err != nil { + return "", err + } + return string(atBytes), nil +} + + func TestHandlers_Requiring_Signature(t *testing.T) { setup(t) + clientJson := "{\"client_id\":\"2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9\",\"client_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"keys\":[{\"public_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"private_key\":\"9fef6ff5edc39a79c1d8e5eb7ca7e5ac14d34615ee49e6d8ca12ecec136f5907\"}],\"mnemonics\":\"expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe\",\"version\":\"1.0\",\"date_created\":\"2021-05-30 17:45:06.492093 +0545 +0545 m=+0.139083805\"}" + client.PopulateClient(clientJson, "bls0chain") router, handlers := setupHandlers() sch := zcncrypto.NewBLS0ChainScheme() + sch.Mnemonic = "expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe" _, err := sch.GenerateKeys() if err != nil { t.Fatal(err) @@ -207,7 +249,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { ts := time.Now().Add(time.Hour) alloc := makeTestAllocation(common.Timestamp(ts.Unix())) alloc.OwnerPublicKey = sch.GetPublicKey() - alloc.OwnerID = sch.GetPublicKey() + alloc.OwnerID = client.GetClientID() const ( path = "/path" @@ -1008,9 +1050,105 @@ func TestHandlers_Requiring_Signature(t *testing.T) { }, wantCode: http.StatusOK, }, + { + name: "InsertShareInfo_OK", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/marketplace/shareinfo/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + shareClientEncryptionPublicKey := "kkk" + shareClientID := "abcdefgh" + formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey) + remotePath := "/file.txt" + filePathHash := "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c" + // _, fileName := filepath.Split(remotePath) + // allocationObj, err := sdk.GetAllocation(alloc.ID) + // if err != nil { + // t.Fatal("Error fetching the allocation", err) + // } + + // shareClientID := "1234568" + authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, filePathHash, shareClientID, sch.GetPublicKey()) + if err != nil { + t.Fatal(err) + } + formWriter.WriteField("auth_ticket", authTicket) + // allocationObj.GetAuthTicket(remotePath, fileName, fileref.FILE, shareClientID, shareClientEncryptionPublicKey) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + t.Log(err) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.Tx, "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnRows( + sqlmock.NewRows([]string{"path", "lookup_hash"}). + AddRow("/file.txt", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`INSERT INTO "marketplace_share_info"`). + WithArgs(client.GetClientID(), "abcdefgh", "/file.txt", "", aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + }, + wantCode: http.StatusOK, + wantBody: "{\"message\":\"Share info added successfully\"}\n\n", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { + if false && !strings.Contains(test.name, "Share") { + continue + } t.Run(test.name, func(t *testing.T) { mock := datastore.MockTheStore(t) test.setupDbMock(mock) diff --git a/code/go/0chain.net/blobbercore/handler/marketplace_test.go b/code/go/0chain.net/blobbercore/handler/marketplace_test.go deleted file mode 100644 index 1bae7912f..000000000 --- a/code/go/0chain.net/blobbercore/handler/marketplace_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package handler - -import ( - bconfig "0chain.net/blobbercore/config" - "0chain.net/blobbercore/datastore" - "0chain.net/blobbercore/filestore" - "0chain.net/blobbercore/reference" - "0chain.net/core/chain" - "0chain.net/core/common" - "0chain.net/core/config" - "0chain.net/core/logging" - "encoding/json" - "fmt" - "github.com/0chain/gosdk/core/zcncrypto" - "github.com/0chain/gosdk/zcncore" - "github.com/DATA-DOG/go-sqlmock" - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" - "net/http" - "net/http/httptest" - "os" - "regexp" - "testing" -) - -func init() { - common.ConfigRateLimits() - chain.SetServerChain(&chain.Chain{}) - config.Configuration.SignatureScheme = "bls0chain" - logging.Logger = zap.NewNop() - - dir, _ := os.Getwd() - if _, err := filestore.SetupFSStore(dir + "/tmp"); err != nil { - panic(err) - } - bconfig.Configuration.MaxFileSize = int64(1 << 30) - bconfig.Configuration.PreEncryption.AutoGenerate = true -} - -func setupTest(t *testing.T) { - // setup wallet - w, err := zcncrypto.NewBLS0ChainScheme().GenerateKeys() - if err != nil { - t.Fatal(err) - } - wBlob, err := json.Marshal(w) - if err != nil { - t.Fatal(err) - } - if err := zcncore.SetWalletInfo(string(wBlob), true); err != nil { - t.Fatal(err) - } - - // setup servers - sharderServ := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - }, - ), - ) - server := httptest.NewServer( - http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - n := zcncore.Network{Miners: []string{"miner 1"}, Sharders: []string{sharderServ.URL}} - blob, err := json.Marshal(n) - if err != nil { - t.Fatal(err) - } - - if _, err := w.Write(blob); err != nil { - t.Fatal(err) - } - }, - ), - ) - - if err := zcncore.InitZCNSDK(server.URL, "ed25519"); err != nil { - t.Fatal(err) - } -} - -func setupHandler() (*mux.Router, map[string]string) { - router := mux.NewRouter() - - marketplacePath := "/v1/marketplace/secret" - mName := "MarketplaceInfo" - router.HandleFunc(marketplacePath, common.UserRateLimit( - common.ToJSONResponse( - WithReadOnlyConnection(MarketPlaceSecretHandler), - ), - ), - ).Name(mName) - - return router, - map[string]string{ - marketplacePath: mName, - } -} - -func TestMarketplaceApi(t *testing.T) { - setupTest(t) - router, handlers := setupHandler() - - t.Run("marketplace_key_existing", func(t *testing.T) { - mock := datastore.MockTheStore(t) - setupDbMock := func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). - WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}). - AddRow("pub", "prv", "a b c d"), - ) - - mock.ExpectCommit() - } - setupDbMock(mock) - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - wantBody := `{"mnemonic":"a b c d"}` + "\n" - assert.Equal(t, wantBody, recorder.Body.String()) - }) - - t.Run("marketplace_key_existing_same_for_all_blobbers_read_from_config", func(t *testing.T) { - datastore.MockTheStore(t) - bconfig.Configuration.PreEncryption.AutoGenerate = false - bconfig.Configuration.PreEncryption.Mnemonic = - "inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown" - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - wantBody := `{"mnemonic":"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown"}` + "\n" - assert.Equal(t, wantBody, recorder.Body.String()) - }) - - t.Run("marketplace_create_new_key_and_return", func(t *testing.T) { - mock := datastore.MockTheStore(t) - setupDbMock := func(mock sqlmock.Sqlmock) { - mock.ExpectBegin() - - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace" ORDER BY "marketplace"."mnemonic" LIMIT 1`)). - WillReturnRows( - sqlmock.NewRows([]string{"public_key", "private_key", "mnemonic"}), - ) - - mock.ExpectExec(`INSERT INTO "marketplace"`). - WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnResult(sqlmock.NewResult(0, 0)) - - - mock.ExpectCommit() - } - setupDbMock(mock) - httprequest := func() *http.Request { - handlerName := handlers["/v1/marketplace/secret"] - - url, err := router.Get(handlerName).URL() - if err != nil { - t.Fatal() - } - - r, err := http.NewRequest(http.MethodGet, url.String(), nil) - if err != nil { - t.Fatal(err) - } - - return r - }() - - recorder := httptest.NewRecorder() - router.ServeHTTP(recorder, httprequest) - assert.Equal(t, 200, 200) - marketplaceInfo := reference.MarketplaceInfo {} - json.Unmarshal([]byte(recorder.Body.String()), &marketplaceInfo) - assert.NotEmpty(t, marketplaceInfo) - assert.Empty(t, marketplaceInfo.PublicKey) - assert.Empty(t, marketplaceInfo.PrivateKey) - assert.NotEmpty(t, marketplaceInfo.Mnemonic) - fmt.Println(marketplaceInfo) - }) - -} diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 15c68de4b..87e644b5b 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -3,8 +3,6 @@ package reference import ( "0chain.net/blobbercore/datastore" "context" - "github.com/0chain/gosdk/core/zcncrypto" - zboxenc "github.com/0chain/gosdk/zboxcore/encryption" "time" ) @@ -38,19 +36,4 @@ func GetShareInfo(ctx context.Context, clientID string, fileName string) (ShareI First(&shareInfo).Error return shareInfo, err -} - -func GetSignatureScheme() zcncrypto.SignatureScheme { - // TODO: bls0chain scheme crashes - return zcncrypto.NewSignatureScheme("ed25519") -} - -func CreateRegenKey(clientEncryptionPublicKey string, tag string) (string, error) { - encscheme := GetSignatureScheme() - key, err := encscheme.GetReGenKey(clientEncryptionPublicKey, tag) - if err != nil { - return "", err - } - return key, nil -} - +} \ No newline at end of file From 8bf7acc0c8362c0f7b4e09865d8b57b37d83fbe8 Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 3 Jun 2021 17:39:06 +0545 Subject: [PATCH 32/66] Update error message on unauthorized download --- .../0chain.net/blobbercore/handler/object_operation_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index f112bcba6..a930ab482 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -408,7 +408,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( // check if client is authorized to download shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.Path) if err != nil { - return nil, err + return nil, errors.New("Client does not have permission to download the file") } buyerEncryptionPublicKey := shareInfo.ClientEncryptionPublicKey From dd629f4af89ace11b59503457d76068095632c1b Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 3 Jun 2021 22:51:23 +0545 Subject: [PATCH 33/66] Update blobber handler --- .../0chain.net/blobbercore/handler/handler_test.go | 13 ++----------- sql/14-add-marketplace-table.sql | 2 ++ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 9671581af..dbc6aa2e1 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -14,7 +14,6 @@ import ( "bytes" "encoding/json" "errors" - "fmt" "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/client" "github.com/0chain/gosdk/zboxcore/fileref" @@ -1068,24 +1067,16 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey) remotePath := "/file.txt" filePathHash := "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c" - // _, fileName := filepath.Split(remotePath) - // allocationObj, err := sdk.GetAllocation(alloc.ID) - // if err != nil { - // t.Fatal("Error fetching the allocation", err) - // } - - // shareClientID := "1234568" authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, filePathHash, shareClientID, sch.GetPublicKey()) if err != nil { t.Fatal(err) } formWriter.WriteField("auth_ticket", authTicket) - // allocationObj.GetAuthTicket(remotePath, fileName, fileref.FILE, shareClientID, shareClientEncryptionPublicKey) if err := formWriter.Close(); err != nil { t.Fatal(err) } r, err := http.NewRequest(http.MethodPost, url.String(), body) - t.Log(err) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) if err != nil { t.Fatal(err) } @@ -1146,7 +1137,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { } tests := append(positiveTests, negativeTests...) for _, test := range tests { - if false && !strings.Contains(test.name, "Share") { + if !strings.Contains(test.name, "Share") { continue } t.Run(test.name, func(t *testing.T) { diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index b81f89fb3..a7aaa1bb6 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -7,6 +7,7 @@ CREATE TABLE marketplace_share_info ( client_id VARCHAR(64) NOT NULL, file_name TEXT NOT NULL, re_encryption_key TEXT NOT NULL, + client_encryption_public_key TEXT NOT NULL, expiry_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() @@ -18,3 +19,4 @@ CREATE INDEX idx_marketplace_share_info_for_client ON marketplace_share_info(cli CREATE TRIGGER share_info_modtime BEFORE UPDATE ON marketplace_share_info FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); GRANT ALL PRIVILEGES ON TABLE marketplace_share_info TO blobber_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO blobber_user; From a6a506984ed18d524abcfe1ac4c5cadf4cdcf7a1 Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 00:15:25 +0545 Subject: [PATCH 34/66] Use filepath hash --- .../0chain.net/blobbercore/handler/handler.go | 17 +++++++++-- .../blobbercore/handler/handler_test.go | 7 +++-- .../handler/object_operation_handler.go | 6 ++-- .../blobbercore/reference/shareinfo.go | 29 +++++++++++++++---- .../blobbercore/reference/shareinfo_test.go | 17 ----------- sql/14-add-marketplace-table.sql | 2 +- 6 files changed, 47 insertions(+), 31 deletions(-) delete mode 100644 code/go/0chain.net/blobbercore/reference/shareinfo_test.go diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 9319396fc..42afa52c1 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -330,6 +330,7 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac } ctx = setupHandlerContext(ctx, r) + allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) if err != nil { @@ -367,19 +368,29 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac shareInfo := reference.ShareInfo{ OwnerID: authTicket.OwnerID, ClientID: authTicket.ClientID, - FileName: authTicket.FileName, + FilePathHash: authTicket.FilePathHash, ReEncryptionKey: authTicket.ReEncryptionKey, ClientEncryptionPublicKey: encryptionPublicKey, ExpiryAt: common.ToTime(authTicket.Expiration), } - err = reference.AddShareInfo(ctx, shareInfo) + existing, err := reference.GetShareInfo(ctx, authTicket.ClientID, authTicket.FilePathHash) + if err != nil { + return nil, err + } + + if existing != nil { + err = reference.UpdateShareInfo(ctx, shareInfo) + } else { + err = reference.AddShareInfo(ctx, shareInfo) + } if err != nil { return nil, err } - resp := map[string]string { + resp := map[string]interface{} { "message": "Share info added successfully", + "existing": existing, } return resp, nil diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index dbc6aa2e1..e7d95a899 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -1125,14 +1125,17 @@ func TestHandlers_Requiring_Signature(t *testing.T) { AddRow("/file.txt", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"), ) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs("abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnRows(sqlmock.NewRows([]string{})) aa := sqlmock.AnyArg() mock.ExpectExec(`INSERT INTO "marketplace_share_info"`). - WithArgs(client.GetClientID(), "abcdefgh", "/file.txt", "", aa, aa). + WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "", aa, aa). WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, - wantBody: "{\"message\":\"Share info added successfully\"}\n\n", + wantBody: "{\"message\":\"Share info added successfully\",\"existing\":false}\n\n", }, } tests := append(positiveTests, negativeTests...) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index a930ab482..538f7701b 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -406,9 +406,11 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.LatestRM = readMarker if attrs.PreAtBlobber { // check if client is authorized to download - shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.Path) + shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.PathHash) if err != nil { - return nil, errors.New("Client does not have permission to download the file") + return nil, errors.New("client does not have permission to download the file, " + err.Error()) + } else if shareInfo == nil { + return nil, errors.New("client does not have permission to download the file. share does not exist") } buyerEncryptionPublicKey := shareInfo.ClientEncryptionPublicKey diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 87e644b5b..19cc80a01 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -3,6 +3,7 @@ package reference import ( "0chain.net/blobbercore/datastore" "context" + "gorm.io/gorm" "time" ) @@ -10,7 +11,7 @@ import ( type ShareInfo struct { OwnerID string `gorm:"owner_id" json:"owner_id,omitempty"` ClientID string `gorm:"client_id" json:"client_id"` - FileName string `gorm:"file_name" json:"file_name,omitempty"` + FilePathHash string `gorm:"file_path_hash" json:"file_path_hash,omitempty"` ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` @@ -25,15 +26,31 @@ func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { return db.Table(TableName()).Create(shareInfo).Error } -func GetShareInfo(ctx context.Context, clientID string, fileName string) (ShareInfo, error) { +func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) - shareInfo := ShareInfo{} + return db.Table(TableName()). + Where(&ShareInfo{ + ClientID: shareInfo.ClientID, + FilePathHash: shareInfo.FilePathHash, + }). + Updates(shareInfo).Error +} + +func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*ShareInfo, error) { + db := datastore.GetStore().GetTransaction(ctx) + shareInfo := &ShareInfo{} err := db.Table(TableName()). Where(&ShareInfo{ ClientID: clientID, - FileName: fileName, + FilePathHash: filePathHash, }). - First(&shareInfo).Error + First(shareInfo).Error - return shareInfo, err + if err == gorm.ErrRecordNotFound { + return nil, nil + } + if err != nil { + return nil, err + } + return shareInfo, nil } \ No newline at end of file diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo_test.go b/code/go/0chain.net/blobbercore/reference/shareinfo_test.go deleted file mode 100644 index faa841ac9..000000000 --- a/code/go/0chain.net/blobbercore/reference/shareinfo_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package reference - -import ( - "fmt" - "github.com/stretchr/testify/require" - "testing" -) - -func TestSecretKeys(t *testing.T) { - result, err := GetSecretKeyPair() - - require.NoError(t, err) - require.NotNil(t, result) - require.NotNil(t, result.PublicKey) - require.NotNil(t, result.PrivateKey) - fmt.Println(result.PublicKey, result.PrivateKey) -} diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index a7aaa1bb6..49d1db86a 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -5,7 +5,7 @@ CREATE TABLE marketplace_share_info ( id BIGSERIAL PRIMARY KEY, owner_id VARCHAR(64) NOT NULL, client_id VARCHAR(64) NOT NULL, - file_name TEXT NOT NULL, + file_path_hash TEXT NOT NULL, re_encryption_key TEXT NOT NULL, client_encryption_public_key TEXT NOT NULL, expiry_at TIMESTAMP NULL, From 4396bb8ea0178e77456ef3bf846d43418a1811e0 Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 08:31:11 +0545 Subject: [PATCH 35/66] Cleanup --- code/go/0chain.net/blobbercore/handler/handler_test.go | 3 --- .../blobbercore/handler/object_operation_handler.go | 2 +- sql/14-add-marketplace-table.sql | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index e7d95a899..d2f691a8e 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -1140,9 +1140,6 @@ func TestHandlers_Requiring_Signature(t *testing.T) { } tests := append(positiveTests, negativeTests...) for _, test := range tests { - if !strings.Contains(test.name, "Share") { - continue - } t.Run(test.name, func(t *testing.T) { mock := datastore.MockTheStore(t) test.setupDbMock(mock) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 538f7701b..8564614a9 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -408,7 +408,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( // check if client is authorized to download shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.PathHash) if err != nil { - return nil, errors.New("client does not have permission to download the file, " + err.Error()) + return nil, errors.New("error during share info lookup in database" + err.Error()) } else if shareInfo == nil { return nil, errors.New("client does not have permission to download the file. share does not exist") } diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index 49d1db86a..26dd4cfde 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -13,8 +13,8 @@ CREATE TABLE marketplace_share_info ( updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); -CREATE INDEX idx_marketplace_share_info_for_owner ON marketplace_share_info(owner_id, file_name); -CREATE INDEX idx_marketplace_share_info_for_client ON marketplace_share_info(client_id, file_name); +CREATE INDEX idx_marketplace_share_info_for_owner ON marketplace_share_info(owner_id, file_path_hash); +CREATE INDEX idx_marketplace_share_info_for_client ON marketplace_share_info(client_id, file_path_hash); CREATE TRIGGER share_info_modtime BEFORE UPDATE ON marketplace_share_info FOR EACH ROW EXECUTE PROCEDURE update_modified_column(); From 2852417fa79e69809a55950a2d6dc0cb1849e084 Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 08:38:42 +0545 Subject: [PATCH 36/66] Remove dead code --- code/go/0chain.net/blobber/main.go | 5 ----- code/go/0chain.net/blobbercore/config/config.go | 7 ------- code/go/0chain.net/blobbercore/handler/handler_test.go | 1 - config/0chain_blobber.yaml | 7 +------ 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/code/go/0chain.net/blobber/main.go b/code/go/0chain.net/blobber/main.go index f54224655..322c27232 100644 --- a/code/go/0chain.net/blobber/main.go +++ b/code/go/0chain.net/blobber/main.go @@ -96,11 +96,6 @@ func SetupWorkerConfig() { config.Configuration.MinLockDemand = viper.GetFloat64("min_lock_demand") config.Configuration.MaxOfferDuration = viper.GetDuration("max_offer_duration") config.Configuration.ChallengeCompletionTime = viper.GetDuration("challenge_completion_time") - config.Configuration.PreEncryption.AutoGenerate = viper.GetBool("pre_encryption.auto_generate") - config.Configuration.PreEncryption.Mnemonic = viper.GetString("pre_encryption.mnemonic") - if !config.Configuration.PreEncryption.AutoGenerate && len(config.Configuration.PreEncryption.Mnemonic) == 0 { - log.Fatal("mnemonic missing for pre_encryption for auto_generate: false") - } config.Configuration.ReadLockTimeout = int64( viper.GetDuration("read_lock_timeout") / time.Second, diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index eb678366e..bf95a8d8f 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -41,7 +41,6 @@ func SetupDefaultConfig() { viper.SetDefault("service_charge", 0.3) viper.SetDefault("update_allocations_interval", time.Duration(-1)) - viper.SetDefault("pre_encryption.autogenerate", true) } /*SetupConfig - setup the configuration system */ @@ -64,11 +63,6 @@ const ( DeploymentMainNet = 2 ) -type PreEncryptionConfig struct { - AutoGenerate bool `json:"auto_generate,omitempty"` - Mnemonic string `json:"mnemonic,omitempty"` -} - type Config struct { *config.Config DBHost string @@ -124,7 +118,6 @@ type Config struct { NumDelegates int `json:"num_delegates"` // ServiceCharge for blobber. ServiceCharge float64 `json:"service_charge"` - PreEncryption PreEncryptionConfig } /*Configuration of the system */ diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index d2f691a8e..03864194e 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -30,7 +30,6 @@ import ( "net/http/httptest" "os" "regexp" - "strings" "testing" "time" ) diff --git a/config/0chain_blobber.yaml b/config/0chain_blobber.yaml index 55e54230d..73201116e 100755 --- a/config/0chain_blobber.yaml +++ b/config/0chain_blobber.yaml @@ -42,7 +42,7 @@ max_file_size: 10485760 #10MB update_allocations_interval: 1m # delegate wallet (must be set) -delegate_wallet: '34d7afcf6b1477d7099845eda897e2d341ad5672ee33f14ddde80eb3f789c58a' +delegate_wallet: '2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9' # min stake allowed, tokens min_stake: 1.0 # max stake allowed, tokens @@ -116,8 +116,3 @@ integration_tests: # lock_interval used by nodes to request server to connect to blockchain # after start lock_interval: 1s - -pre_encryption: - auto_generate: false - mnemonic: 'inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown' - From fdf02f2c20f434e4c7d5b2507149425cd5d2179b Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 08:40:28 +0545 Subject: [PATCH 37/66] Remove dead code --- code/go/0chain.net/blobbercore/handler/handler_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 03864194e..de6327029 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -45,7 +45,6 @@ func init() { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) - bconfig.Configuration.PreEncryption.AutoGenerate = true } func setup(t *testing.T) { From 93d035e918b87a3d82bbed6037e2c33fc3a5f237 Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 19:39:59 +0545 Subject: [PATCH 38/66] Add test for upload --- .../blobbercore/handler/handler_test.go | 96 ++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index de6327029..2a24ff21c 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -218,6 +218,7 @@ func GetAuthTicketForEncryptedFile(allocationID string, remotePath string, fileH timestamp := int64(common.Now()) at.Expiration = timestamp + 7776000 at.Timestamp = timestamp + at.ReEncryptionKey = "regenkey" err := at.Sign() if err != nil { return "", err @@ -1048,7 +1049,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusOK, }, { - name: "InsertShareInfo_OK", + name: "InsertShareInfo_OK_New_Share", args: args{ w: httptest.NewRecorder(), r: func() *http.Request { @@ -1129,12 +1130,103 @@ func TestHandlers_Requiring_Signature(t *testing.T) { aa := sqlmock.AnyArg() mock.ExpectExec(`INSERT INTO "marketplace_share_info"`). - WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "", aa, aa). + WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "regenkey", aa, aa). WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, wantBody: "{\"message\":\"Share info added successfully\",\"existing\":false}\n\n", }, + { + name: "UpdateShareInfo", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/marketplace/shareinfo/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + shareClientEncryptionPublicKey := "kkk" + shareClientID := "abcdefgh" + formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey) + remotePath := "/file.txt" + filePathHash := "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c" + authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, filePathHash, shareClientID, sch.GetPublicKey()) + if err != nil { + t.Fatal(err) + } + formWriter.WriteField("auth_ticket", authTicket) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.Tx, "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnRows( + sqlmock.NewRows([]string{"path", "lookup_hash"}). + AddRow("/file.txt", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs("abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow("abcdefgh"), + ) + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "marketplace_share_info"`). + WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "regenkey", aa, aa, "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnResult(sqlmock.NewResult(0, 0)) + }, + wantCode: http.StatusOK, + wantBody: "{\"message\":\"Share info added successfully\",\"existing\":true}\n\n", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { From 009b45cddaa2c95a817a99d9ae402c57a81069e4 Mon Sep 17 00:00:00 2001 From: Uk Date: Fri, 4 Jun 2021 21:57:43 +0545 Subject: [PATCH 39/66] Add proxy encryption download UML --- .../handler/object_operation_handler.go | 4 ++++ .../0chain.net/blobbercore/readmarker/entity.go | 1 + docs/src/proxy_encryption_download_flow.plantuml | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 docs/src/proxy_encryption_download_flow.plantuml diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 8564614a9..8a147af99 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -313,6 +313,10 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "error parsing the auth ticket for download: %v", err) } + if authToken.ContentHash != fileref.ContentHash { + return nil, errors.New("content hash does not match the requested file content hash") + } + // if --rx_pay used 3rd_party pays if rxPay { clientIDForReadRedeem = clientID diff --git a/code/go/0chain.net/blobbercore/readmarker/entity.go b/code/go/0chain.net/blobbercore/readmarker/entity.go index be13cd12c..4c364f9a9 100644 --- a/code/go/0chain.net/blobbercore/readmarker/entity.go +++ b/code/go/0chain.net/blobbercore/readmarker/entity.go @@ -21,6 +21,7 @@ type AuthTicket struct { OwnerID string `json:"owner_id"` AllocationID string `json:"allocation_id"` FilePathHash string `json:"file_path_hash"` + ContentHash string `json:"content_hash"` FileName string `json:"file_name"` RefType string `json:"reference_type"` Expiration common.Timestamp `json:"expiration"` diff --git a/docs/src/proxy_encryption_download_flow.plantuml b/docs/src/proxy_encryption_download_flow.plantuml new file mode 100644 index 000000000..1857c8847 --- /dev/null +++ b/docs/src/proxy_encryption_download_flow.plantuml @@ -0,0 +1,15 @@ +@startuml + +actor Client + + +loop till all of requested file chunks are completely downloaded +Client -> Blobber : Request the file in range of blocks x..y +Blobber --> Database : Get reencryption key from postgres db table \n\ +for the client +Blobber --> Blobber : Iterate through all chunks and \n\ +encrypt every chunk using re-encryption key +Blobber --> Client : Return Re-encrypted message +end + +@enduml From d0d853d454a5adba74b61eedc1a47b0a4dc6ee49 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 7 Jun 2021 16:55:13 +0545 Subject: [PATCH 40/66] Update uml --- docs/src/proxy_encryption_download_flow.plantuml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/proxy_encryption_download_flow.plantuml b/docs/src/proxy_encryption_download_flow.plantuml index 1857c8847..74a28886c 100644 --- a/docs/src/proxy_encryption_download_flow.plantuml +++ b/docs/src/proxy_encryption_download_flow.plantuml @@ -10,6 +10,7 @@ for the client Blobber --> Blobber : Iterate through all chunks and \n\ encrypt every chunk using re-encryption key Blobber --> Client : Return Re-encrypted message +Client --> Client : Re-decrypt the received message using private key end @enduml From d59f6eaa195b57216be9a35d10929e4a4bd96f61 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 8 Jun 2021 18:28:47 +0545 Subject: [PATCH 41/66] Remove pre at blobber --- .../0chain.net/blobbercore/handler/object_operation_handler.go | 2 +- code/go/0chain.net/blobbercore/reference/ref.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 8a147af99..9e339d060 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -408,7 +408,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( var response = &DownloadResponse{} response.Success = true response.LatestRM = readMarker - if attrs.PreAtBlobber { + if len(fileref.EncryptedKey) > 0 { // check if client is authorized to download shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.PathHash) if err != nil { diff --git a/code/go/0chain.net/blobbercore/reference/ref.go b/code/go/0chain.net/blobbercore/reference/ref.go index 260b8a109..f422f501f 100644 --- a/code/go/0chain.net/blobbercore/reference/ref.go +++ b/code/go/0chain.net/blobbercore/reference/ref.go @@ -35,7 +35,6 @@ type Attributes struct { // or a 3rd party user. It affects read operations only. It requires // blobbers to be trusted. WhoPaysForReads common.WhoPays `json:"who_pays_for_reads,omitempty"` - PreAtBlobber bool `json:"pre_at_blobber,omitempty"` // add more file / directory attributes by needs with // 'omitempty' json tag to avoid hash difference for From 7432dfa7a6ad51c322d0b44780bde993eca7e3f7 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 15 Jun 2021 22:04:43 +0545 Subject: [PATCH 42/66] Add remove share handler --- .../0chain.net/blobbercore/handler/handler.go | 62 ++++++++++++++++++- .../blobbercore/reference/shareinfo.go | 9 +++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 42afa52c1..4b75ea34b 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -8,6 +8,8 @@ import ( "context" "encoding/json" "errors" + "github.com/0chain/gosdk/zboxcore/fileref" + "gorm.io/gorm" "net/http" "os" "runtime/pprof" @@ -324,11 +326,53 @@ func CleanupDiskHandler(ctx context.Context, r *http.Request) (interface{}, erro return "cleanup", err } -func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interface{}, error) { - if r.Method != "POST" { - return nil, errors.New("invalid request method, only POST is allowed") +func RevokeShare(ctx context.Context, r *http.Request) (interface{}, error) { + ctx = setupHandlerContext(ctx, r) + + allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) + allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) + if err != nil { + return nil, common.NewError("invalid_parameters", "Invalid allocation id passed." + err.Error()) } + valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + if !valid || err != nil { + return nil, common.NewError("invalid_signature", "Invalid signature") + } + + path := r.FormValue("path") + refereeClientID := r.FormValue("refereeClientID") + filePathHash := fileref.GetReferenceLookup(allocationID, path) + _, err = reference.GetReferenceFromLookupHash(ctx, allocationID, filePathHash) + if err != nil { + return nil, common.NewError("invalid_parameters", "Invalid file path. " + err.Error()) + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) + if clientID != allocationObj.OwnerID { + return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") + } + err = reference.DeleteShareInfo(ctx, reference.ShareInfo { + ClientID: refereeClientID, + FilePathHash: filePathHash, + }) + if errors.Is(err, gorm.ErrRecordNotFound) { + resp := map[string]interface{} { + "status": http.StatusNotFound, + "message": "Path not found", + } + return resp, nil + } + if err != nil { + return nil, err + } + resp := map[string]interface{} { + "status": http.StatusNoContent, + "message": "Path successfully removed from allocation", + } + return resp, nil +} + +func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { ctx = setupHandlerContext(ctx, r) allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) @@ -396,3 +440,15 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac return resp, nil } +func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interface{}, error) { + if r.Method == "DELETE" { + return RevokeShare(ctx, r) + } + + if r.Method == "POST" { + return InsertShare(ctx, r) + } + + return nil, errors.New("invalid request method, only POST is allowed") +} + diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 19cc80a01..25cfd9188 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -26,6 +26,15 @@ func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { return db.Table(TableName()).Create(shareInfo).Error } +func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { + db := datastore.GetStore().GetTransaction(ctx) + return db.Table(TableName()). + Delete(&ShareInfo{ + ClientID: shareInfo.ClientID, + FilePathHash: shareInfo.FilePathHash, + }).Error +} + func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) return db.Table(TableName()). From 5bb2e859711043ba28e952aa8fcaed6059ef5030 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 15 Jun 2021 22:26:39 +0545 Subject: [PATCH 43/66] Fix blobber get hash data --- code/go/0chain.net/blobbercore/readmarker/entity.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/readmarker/entity.go b/code/go/0chain.net/blobbercore/readmarker/entity.go index 4c364f9a9..d01030834 100644 --- a/code/go/0chain.net/blobbercore/readmarker/entity.go +++ b/code/go/0chain.net/blobbercore/readmarker/entity.go @@ -28,10 +28,11 @@ type AuthTicket struct { Timestamp common.Timestamp `json:"timestamp"` ReEncryptionKey string `json:"re_encryption_key"` Signature string `json:"signature"` + Encrypted bool `json:"encrypted"` } func (rm *AuthTicket) GetHashData() string { - hashData := fmt.Sprintf("%v:%v:%v:%v:%v:%v:%v:%v:%v", rm.AllocationID, rm.ClientID, rm.OwnerID, rm.FilePathHash, rm.FileName, rm.RefType, rm.ReEncryptionKey, rm.Expiration, rm.Timestamp) + hashData := fmt.Sprintf("%v:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v", rm.AllocationID, rm.ClientID, rm.OwnerID, rm.FilePathHash, rm.FileName, rm.RefType, rm.ReEncryptionKey, rm.Expiration, rm.Timestamp, rm.ContentHash, rm.Encrypted) return hashData } From d0002c5dba8911841c94a61e0414bebfc24c7d71 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 15 Jun 2021 23:08:43 +0545 Subject: [PATCH 44/66] Add revoke api test --- .../blobbercore/handler/handler_test.go | 165 +++++++++++++++++- .../blobbercore/reference/shareinfo.go | 8 +- 2 files changed, 168 insertions(+), 5 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 2a24ff21c..4b3fcb9f1 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -30,6 +30,7 @@ import ( "net/http/httptest" "os" "regexp" + "strings" "testing" "time" ) @@ -1227,9 +1228,171 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusOK, wantBody: "{\"message\":\"Share info added successfully\",\"existing\":true}\n\n", }, + { + name: "RevokeShareInfo_OK_Existing_Share", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/marketplace/shareinfo/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + shareClientID := "abcdefgh" + remotePath := "/file.txt" + + formWriter.WriteField("refereeClientID", shareClientID) + formWriter.WriteField("path", remotePath) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodDelete, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.Tx, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "lookup_hash"}). + AddRow("/file.txt", filePathHash), + ) + + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "marketplace_share_info" WHERE`)). + WithArgs("abcdefgh", filePathHash). + WillReturnResult(sqlmock.NewResult(0, 1)) + + }, + wantCode: http.StatusOK, + wantBody: "{\"message\":\"Path successfully removed from allocation\",\"status\":204}\n", + }, + { + name: "RevokeShareInfo_NotOK_For_Non_Existing_Share", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/marketplace/shareinfo/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + shareClientID := "abcdefgh" + remotePath := "/file.txt" + + formWriter.WriteField("refereeClientID", shareClientID) + formWriter.WriteField("path", remotePath) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodDelete, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.Tx, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "lookup_hash"}). + AddRow("/file.txt", filePathHash), + ) + + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "marketplace_share_info" WHERE`)). + WithArgs("abcdefgh", filePathHash).WillReturnError(gorm.ErrRecordNotFound) + + }, + wantCode: http.StatusOK, + wantBody: "{\"message\":\"Path not found\",\"status\":404}\n", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { + if !strings.Contains(test.name, "Revoke") { + continue + } t.Run(test.name, func(t *testing.T) { mock := datastore.MockTheStore(t) test.setupDbMock(mock) @@ -1237,7 +1400,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { router.ServeHTTP(test.args.w, test.args.r) assert.Equal(t, test.wantCode, test.args.w.Result().StatusCode) - if test.wantCode != http.StatusOK { + if test.wantCode != http.StatusOK || test.wantBody != "" { assert.Equal(t, test.wantBody, test.args.w.Body.String()) } }) diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 25cfd9188..10a8337c1 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -29,10 +29,10 @@ func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) return db.Table(TableName()). - Delete(&ShareInfo{ - ClientID: shareInfo.ClientID, - FilePathHash: shareInfo.FilePathHash, - }).Error + Where("client_id = ?", shareInfo.ClientID). + Where("file_path_hash = ?", shareInfo.FilePathHash). + Delete(&ShareInfo{}). + Error } func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { From 641e64b48d7faea03e1503ab807fd2f668b03821 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 16 Jun 2021 20:08:34 +0545 Subject: [PATCH 45/66] Fix db handler --- .../0chain.net/blobbercore/reference/shareinfo.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 10a8337c1..5d2ac8d8b 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -28,11 +28,18 @@ func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) - return db.Table(TableName()). + result := db.Table(TableName()). Where("client_id = ?", shareInfo.ClientID). Where("file_path_hash = ?", shareInfo.FilePathHash). - Delete(&ShareInfo{}). - Error + Delete(&ShareInfo{}) + + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil } func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { From 2bbfcb4c02f3ce11d18a899a516279a96a9ddcec Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 17 Jun 2021 18:03:03 +0545 Subject: [PATCH 46/66] Add test for download file --- .../blobbercore/handler/handler_test.go | 109 +++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 4b3fcb9f1..23c985a11 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -14,6 +14,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/client" "github.com/0chain/gosdk/zboxcore/fileref" @@ -174,6 +175,15 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(uName) + dPath := "/v1/file/download/{allocation}" + dName := "Download" + router.HandleFunc(dPath, common.UserRateLimit( + common.ToJSONResponse( + WithConnection(DownloadHandler), + ), + ), + ).Name(dName) + sharePath := "/v1/marketplace/shareinfo/{allocation}" shareName := "Share" router.HandleFunc(sharePath, common.UserRateLimit( @@ -196,12 +206,22 @@ func setupHandlers() (*mux.Router, map[string]string) { aPath: aName, uPath: uName, sharePath: shareName, + dPath: dName, } } +func isEndpointRequireSignature(name string) bool { + switch name { + case "Download": + return false + default: + return true + } +} + func isEndpointAllowGetReq(name string) bool { switch name { - case "Stats", "Rename", "Copy", "Attributes", "Upload", "Share": + case "Stats", "Rename", "Copy", "Attributes", "Upload", "Share", "Download": return false default: return true @@ -272,6 +292,9 @@ func TestHandlers_Requiring_Signature(t *testing.T) { ) negativeTests := make([]test, 0) for _, name := range handlers { + if !isEndpointRequireSignature(name) { + continue + } baseSetupDbMock := func(mock sqlmock.Sqlmock) { mock.ExpectBegin() @@ -1387,6 +1410,90 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusOK, wantBody: "{\"message\":\"Path not found\",\"status\":404}\n", }, + { + name: "DownloadFile_Record_Not_Found", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + remotePath := "/file.txt" + + formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + rm := &marker.ReadMarker{} + rm.ClientID = client.GetClientID() + rm.ClientPublicKey = client.GetClientPublicKey() + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.OwnerID = client.GetClientID() + err = rm.Sign() + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + formWriter.WriteField("read_marker", string(rmData)) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash).WillReturnError(gorm.ErrRecordNotFound) + + }, + wantCode: http.StatusBadRequest, + wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: invalid file path: record not found\"}\n\n", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { From 62b01625a3d958ecfe0fcb1480ddc3adf4a3a326 Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 17 Jun 2021 20:50:41 +0545 Subject: [PATCH 47/66] Add mock file store --- .../blobbercore/filestore/fs_store.go | 114 +++++++++------- .../blobbercore/handler/handler_test.go | 123 +++++++++++++++++- 2 files changed, 190 insertions(+), 47 deletions(-) diff --git a/code/go/0chain.net/blobbercore/filestore/fs_store.go b/code/go/0chain.net/blobbercore/filestore/fs_store.go index edb41a286..39c99426c 100644 --- a/code/go/0chain.net/blobbercore/filestore/fs_store.go +++ b/code/go/0chain.net/blobbercore/filestore/fs_store.go @@ -43,9 +43,66 @@ type MinioConfiguration struct { var MinioConfig MinioConfiguration +type IFileBlockGetter interface { + GetFileBlock(fsStore *FileFSStore, allocationID string, fileData *FileInputData, blockNum int64, numBlocks int64) ([]byte, error) +} + +type FileBlockGetter struct { +} + +func (FileBlockGetter) GetFileBlock(fs *FileFSStore, allocationID string, fileData *FileInputData, blockNum int64, numBlocks int64) ([]byte, error) { + allocation, err := fs.SetupAllocation(allocationID, true) + if err != nil { + return nil, common.NewError("invalid_allocation", "Invalid allocation. "+err.Error()) + } + dirPath, destFile := GetFilePathFromHash(fileData.Hash) + fileObjectPath := filepath.Join(allocation.ObjectsPath, dirPath) + fileObjectPath = filepath.Join(fileObjectPath, destFile) + + file, err := os.Open(fileObjectPath) + if err != nil { + if os.IsNotExist(err) && fileData.OnCloud { + err = fs.DownloadFromCloud(fileData.Hash, fileObjectPath) + if err != nil { + return nil, common.NewError("minio_download_failed", "Unable to download from minio with err "+err.Error()) + } + file, err = os.Open(fileObjectPath) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + defer file.Close() + fileinfo, err := file.Stat() + if err != nil { + return nil, err + } + + filesize := int(fileinfo.Size()) + maxBlockNum := int64(filesize / CHUNK_SIZE) + // check for any left over bytes. Add one more go routine if required. + if remainder := filesize % CHUNK_SIZE; remainder != 0 { + maxBlockNum++ + } + + if blockNum > maxBlockNum || blockNum < 1 { + return nil, common.NewError("invalid_block_number", "Invalid block number") + } + buffer := make([]byte, CHUNK_SIZE*numBlocks) + n, err := file.ReadAt(buffer, ((blockNum - 1) * CHUNK_SIZE)) + if err != nil && err != io.EOF { + return nil, err + } + + return buffer[:n], nil +} + type FileFSStore struct { RootDirectory string Minio *minio.Client + fileBlockGetter IFileBlockGetter } type StoreAllocation struct { @@ -62,6 +119,16 @@ func SetupFSStore(rootDir string) (FileStore, error) { fsStore = &FileFSStore{ RootDirectory: rootDir, Minio: intializeMinio(), + fileBlockGetter: FileBlockGetter{}, + } + return fsStore, nil +} + +func SetupMockFSStore(rootDir string, fileBlockGetter IFileBlockGetter) (FileStore, error) { + fsStore = &FileFSStore{ + RootDirectory: rootDir, + Minio: intializeMinio(), + fileBlockGetter: fileBlockGetter, } return fsStore, nil } @@ -278,52 +345,7 @@ func (fs *FileFSStore) GetFileBlockForChallenge(allocationID string, fileData *F } func (fs *FileFSStore) GetFileBlock(allocationID string, fileData *FileInputData, blockNum int64, numBlocks int64) ([]byte, error) { - allocation, err := fs.SetupAllocation(allocationID, true) - if err != nil { - return nil, common.NewError("invalid_allocation", "Invalid allocation. "+err.Error()) - } - dirPath, destFile := GetFilePathFromHash(fileData.Hash) - fileObjectPath := filepath.Join(allocation.ObjectsPath, dirPath) - fileObjectPath = filepath.Join(fileObjectPath, destFile) - - file, err := os.Open(fileObjectPath) - if err != nil { - if os.IsNotExist(err) && fileData.OnCloud { - err = fs.DownloadFromCloud(fileData.Hash, fileObjectPath) - if err != nil { - return nil, common.NewError("minio_download_failed", "Unable to download from minio with err "+err.Error()) - } - file, err = os.Open(fileObjectPath) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - defer file.Close() - fileinfo, err := file.Stat() - if err != nil { - return nil, err - } - - filesize := int(fileinfo.Size()) - maxBlockNum := int64(filesize / CHUNK_SIZE) - // check for any left over bytes. Add one more go routine if required. - if remainder := filesize % CHUNK_SIZE; remainder != 0 { - maxBlockNum++ - } - - if blockNum > maxBlockNum || blockNum < 1 { - return nil, common.NewError("invalid_block_number", "Invalid block number") - } - buffer := make([]byte, CHUNK_SIZE*numBlocks) - n, err := file.ReadAt(buffer, ((blockNum - 1) * CHUNK_SIZE)) - if err != nil && err != io.EOF { - return nil, err - } - - return buffer[:n], nil + return fs.fileBlockGetter.GetFileBlock(fs, allocationID, fileData, blockNum, numBlocks) } func (fs *FileFSStore) DeleteTempFile(allocationID string, fileData *FileInputData, connectionID string) error { diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 23c985a11..1d03fb134 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -36,6 +36,20 @@ import ( "time" ) +type MockFileBlockGetter struct { + filestore.IFileBlockGetter +} + +func (MockFileBlockGetter) GetFileBlock( + fsStore *filestore.FileFSStore, + allocationID string, + fileData *filestore.FileInputData, + blockNum int64, + numBlocks int64, +) ([]byte, error) { + return []byte("mock"), nil +} + func init() { common.ConfigRateLimits() chain.SetServerChain(&chain.Chain{}) @@ -43,7 +57,7 @@ func init() { logging.Logger = zap.NewNop() dir, _ := os.Getwd() - if _, err := filestore.SetupFSStore(dir + "/tmp"); err != nil { + if _, err := filestore.SetupMockFSStore(dir + "/tmp", MockFileBlockGetter{}); err != nil { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) @@ -1494,6 +1508,113 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusBadRequest, wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: invalid file path: record not found\"}\n\n", }, + { + name: "DownloadFile_Unencrypted_return_mock", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + remotePath := "/file.txt" + + formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + rm := &marker.ReadMarker{} + rm.ClientID = client.GetClientID() + rm.ClientPublicKey = client.GetClientPublicKey() + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = client.GetClientID() + err = rm.Sign() + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + formWriter.WriteField("read_marker", string(rmData)) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "lookup_hash", "content_hash"}). + AddRow("/file.txt", "f", filePathHash, "abcd"), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) FROM "collaborators" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(client.GetClientID()), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "\"bW9jaw==\"\n", //base64encoded for mock string + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { From acaadefa5213b999fd78ca6363dbe51b5c549a00 Mon Sep 17 00:00:00 2001 From: Uk Date: Sat, 19 Jun 2021 17:43:35 +0545 Subject: [PATCH 48/66] Add test for object_operation_handler download --- .../blobbercore/handler/handler_test.go | 113 +++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 1d03fb134..503f3b05b 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -1509,7 +1509,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantBody: "{\"code\":\"download_file\",\"error\":\"download_file: invalid file path: record not found\"}\n\n", }, { - name: "DownloadFile_Unencrypted_return_mock", + name: "DownloadFile_Unencrypted_return_file", args: args{ w: httptest.NewRecorder(), r: func() *http.Request { @@ -1615,6 +1615,117 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusOK, wantBody: "\"bW9jaw==\"\n", //base64encoded for mock string }, + { + name: "DownloadFile_Encrypted_Permission_Denied_Unshared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + remotePath := "/file.txt" + + formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + rm := &marker.ReadMarker{} + rm.ClientID = client.GetClientID() + rm.ClientPublicKey = client.GetClientPublicKey() + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = client.GetClientID() + err = rm.Sign() + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + formWriter.WriteField("read_marker", string(rmData)) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "abcd", "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio="), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) FROM "collaborators" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(client.GetClientID()), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(client.GetClientID(), filePathHash). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectCommit() + }, + wantCode: http.StatusBadRequest, + wantBody: "{\"error\":\"client does not have permission to download the file. share does not exist\"}\n\n", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { From 4f3057147e7742fe77ee52a0e5829834b05d094a Mon Sep 17 00:00:00 2001 From: Uk Date: Sat, 19 Jun 2021 19:09:36 +0545 Subject: [PATCH 49/66] Add test for reencrypt --- .../blobbercore/handler/handler_test.go | 163 +++++++++++++++++- 1 file changed, 161 insertions(+), 2 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 503f3b05b..722fff858 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -17,6 +17,7 @@ import ( "fmt" "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/client" + zencryption "github.com/0chain/gosdk/zboxcore/encryption" "github.com/0chain/gosdk/zboxcore/fileref" "github.com/0chain/gosdk/zboxcore/marker" "github.com/0chain/gosdk/zcncore" @@ -40,6 +41,8 @@ type MockFileBlockGetter struct { filestore.IFileBlockGetter } +var mockFileBlock []byte + func (MockFileBlockGetter) GetFileBlock( fsStore *filestore.FileFSStore, allocationID string, @@ -47,10 +50,28 @@ func (MockFileBlockGetter) GetFileBlock( blockNum int64, numBlocks int64, ) ([]byte, error) { - return []byte("mock"), nil + return []byte(mockFileBlock), nil +} + +func setMockFileBlock(data []byte) { + mockFileBlock = data +} + +func resetMockFileBlock() { + mockFileBlock = []byte("mock") +} + +var encscheme zencryption.EncryptionScheme + +func setupEncryptionScheme() { + encscheme = zencryption.NewEncryptionScheme() + mnemonic := client.GetClient().Mnemonic + encscheme.Initialize(mnemonic) + encscheme.InitForEncryption("filetype:audio") } func init() { + resetMockFileBlock() common.ConfigRateLimits() chain.SetServerChain(&chain.Chain{}) config.Configuration.SignatureScheme = "bls0chain" @@ -271,6 +292,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { clientJson := "{\"client_id\":\"2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9\",\"client_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"keys\":[{\"public_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"private_key\":\"9fef6ff5edc39a79c1d8e5eb7ca7e5ac14d34615ee49e6d8ca12ecec136f5907\"}],\"mnemonics\":\"expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe\",\"version\":\"1.0\",\"date_created\":\"2021-05-30 17:45:06.492093 +0545 +0545 m=+0.139083805\"}" client.PopulateClient(clientJson, "bls0chain") + setupEncryptionScheme() router, handlers := setupHandlers() sch := zcncrypto.NewBLS0ChainScheme() @@ -300,6 +322,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { args args alloc *allocation.Allocation setupDbMock func(mock sqlmock.Sqlmock) + begin func() + end func() wantCode int wantBody string } @@ -1726,17 +1750,152 @@ func TestHandlers_Requiring_Signature(t *testing.T) { wantCode: http.StatusBadRequest, wantBody: "{\"error\":\"client does not have permission to download the file. share does not exist\"}\n\n", }, + { + name: "DownloadFile_Encrypted_Permission_Allowed_shared_File", + args: args{ + w: httptest.NewRecorder(), + r: func() *http.Request { + handlerName := handlers["/v1/file/download/{allocation}"] + url, err := router.Get(handlerName).URL("allocation", alloc.Tx) + if err != nil { + t.Fatal() + } + + body := bytes.NewBuffer(nil) + formWriter := multipart.NewWriter(body) + remotePath := "/file.txt" + + formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + rm := &marker.ReadMarker{} + rm.ClientID = client.GetClientID() + rm.ClientPublicKey = client.GetClientPublicKey() + rm.BlobberID = "" + rm.AllocationID = alloc.ID + rm.ReadCounter = 1 + rm.OwnerID = client.GetClientID() + err = rm.Sign() + if err != nil { + t.Fatal(err) + } + rmData, err := json.Marshal(rm) + formWriter.WriteField("read_marker", string(rmData)) + if err := formWriter.Close(); err != nil { + t.Fatal(err) + } + r, err := http.NewRequest(http.MethodPost, url.String(), body) + r.Header.Add("Content-Type", formWriter.FormDataContentType()) + if err != nil { + t.Fatal(err) + } + + hash := encryption.Hash(alloc.Tx) + sign, err := sch.Sign(hash) + if err != nil { + t.Fatal(err) + } + + r.Header.Set("Content-Type", formWriter.FormDataContentType()) + r.Header.Set(common.ClientSignatureHeader, sign) + r.Header.Set(common.ClientHeader, alloc.OwnerID) + r.Header.Set(common.ClientKeyHeader, alloc.OwnerPublicKey) + + return r + }(), + }, + alloc: alloc, + begin: func() { + dataToEncrypt := "data_to_encrypt" + encMsg, err := encscheme.Encrypt([]byte(dataToEncrypt)) + if err != nil { + t.Fatal(err) + } + header := make([]byte, 2*1024) + copy(header[:], encMsg.MessageChecksum+ "," + encMsg.OverallChecksum) + data := append(header, encMsg.EncryptedData...) + setMockFileBlock(data) + }, + end: func() { + resetMockFileBlock() + }, + setupDbMock: func(mock sqlmock.Sqlmock) { + mock.ExpectBegin() + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocations" WHERE`)). + WithArgs(alloc.Tx). + WillReturnRows( + sqlmock.NewRows( + []string{ + "id", "tx", "expiration_date", "owner_public_key", "owner_id", "blobber_size", + }, + ). + AddRow( + alloc.ID, alloc.Tx, alloc.Expiration, alloc.OwnerPublicKey, alloc.OwnerID, int64(1<<30), + ), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "terms" WHERE`)). + WithArgs(alloc.ID). + WillReturnRows( + sqlmock.NewRows([]string{"id", "allocation_id"}). + AddRow(alloc.Terms[0].ID, alloc.Terms[0].AllocationID), + ) + + filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). + WithArgs(alloc.ID, filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key"}). + AddRow("/file.txt", "f", filePathHash, filePathHash, "abcd", encscheme.GetEncryptedKey()), + ) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) FROM "collaborators" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnError(gorm.ErrRecordNotFound) + + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "read_markers" WHERE`)). + WithArgs(client.GetClientID()). + WillReturnRows( + sqlmock.NewRows([]string{"client_id"}). + AddRow(client.GetClientID()), + ) + + aa := sqlmock.AnyArg() + + mock.ExpectExec(`UPDATE "read_markers"`). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa). + WillReturnResult(sqlmock.NewResult(0, 0)) + + reEncryptionKey, _ := encscheme.GetReGenKey(encscheme.GetEncryptedKey(), "filetype:audio") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). + WithArgs(client.GetClientID(), filePathHash). + WillReturnRows( + sqlmock.NewRows([]string{"re_encryption_key", "client_encryption_public_key"}). + AddRow(reEncryptionKey, encscheme.GetEncryptedKey()), + ) + + mock.ExpectCommit() + }, + wantCode: http.StatusOK, + wantBody: "", + }, } tests := append(positiveTests, negativeTests...) for _, test := range tests { - if !strings.Contains(test.name, "Revoke") { + if !strings.Contains(test.name, "Download") { continue } t.Run(test.name, func(t *testing.T) { mock := datastore.MockTheStore(t) test.setupDbMock(mock) + if test.begin != nil { + test.begin() + } router.ServeHTTP(test.args.w, test.args.r) + if test.end != nil { + test.end() + } assert.Equal(t, test.wantCode, test.args.w.Result().StatusCode) if test.wantCode != http.StatusOK || test.wantBody != "" { From 45d26a56c4083eb8b840fcfff7c0ce09abb9b277 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 30 Jun 2021 19:16:10 +0545 Subject: [PATCH 50/66] Enable folder meta --- code/go/0chain.net/blobbercore/handler/storage_handler.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index 6c15d55c1..a8fdc366b 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -148,10 +148,6 @@ func (fsh *StorageHandler) GetFileMeta(ctx context.Context, r *http.Request) (in return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } - if fileref.Type != reference.FILE { - return nil, common.NewError("invalid_parameters", "Path is not a file.") - } - result := fileref.GetListingData(ctx) commitMetaTxns, err := reference.GetCommitMetaTxns(ctx, fileref.ID) From b970bf264c4d9fdfa5b17fec8e941b966b0396fd Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 30 Jun 2021 22:45:48 +0545 Subject: [PATCH 51/66] Modify get share logic on download to check for parent folder share --- .../handler/object_operation_handler.go | 11 +++++++-- .../blobbercore/reference/shareinfo.go | 23 ++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 9e339d060..3cf868072 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -27,6 +27,7 @@ import ( "0chain.net/core/lock" "0chain.net/core/node" zencryption "github.com/0chain/gosdk/zboxcore/encryption" + zfileref "github.com/0chain/gosdk/zboxcore/fileref" "gorm.io/datatypes" "gorm.io/gorm" @@ -313,7 +314,8 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "error parsing the auth ticket for download: %v", err) } - if authToken.ContentHash != fileref.ContentHash { + // we only check content hash if its authticket is referring to a file + if authToken.RefType == zfileref.FILE && authToken.ContentHash != fileref.ContentHash { return nil, errors.New("content hash does not match the requested file content hash") } @@ -410,7 +412,12 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.LatestRM = readMarker if len(fileref.EncryptedKey) > 0 { // check if client is authorized to download - shareInfo, err := reference.GetShareInfo(ctx, readMarker.ClientID, fileref.PathHash) + shareInfo, err := reference.GetShareInfoRecursive( + ctx, + readMarker.ClientID, + allocationTx, + fileref.Path, + ) if err != nil { return nil, errors.New("error during share info lookup in database" + err.Error()) } else if shareInfo == nil { diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 5d2ac8d8b..ae2d93ef8 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -3,7 +3,9 @@ package reference import ( "0chain.net/blobbercore/datastore" "context" + "github.com/0chain/gosdk/zboxcore/fileref" "gorm.io/gorm" + "strings" "time" ) @@ -69,4 +71,23 @@ func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*S return nil, err } return shareInfo, nil -} \ No newline at end of file +} + +func GetShareInfoRecursive(ctx context.Context, clientID string, allocationTx string, filePath string) (*ShareInfo, error) { + splitted := strings.Split(filePath, "/") + for i := 0; i < len(splitted); i++ { + path := strings.Join(splitted[:len(splitted) - i], "/") + if path == "" { + path = "/" + } + pathHash := fileref.GetReferenceLookup(allocationTx, path) + shareInfo, err := GetShareInfo(ctx, clientID, pathHash) + if err != nil { + return nil, err + } + if shareInfo != nil { + return shareInfo, nil + } + } + return nil, nil +} From f561fe888fd321f3d2458662f83321b8390cffaf Mon Sep 17 00:00:00 2001 From: Uk Date: Thu, 1 Jul 2021 18:34:15 +0545 Subject: [PATCH 52/66] Updates to logic --- .../blobbercore/handler/handler_test.go | 26 +++++++++++++----- .../handler/object_operation_handler.go | 27 ++++++++++++++++--- .../blobbercore/reference/shareinfo.go | 20 -------------- code/go/0chain.net/blobbercore/util/json.go | 16 +++++++++++ 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index 722fff858..d055f1007 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -270,6 +270,7 @@ func GetAuthTicketForEncryptedFile(allocationID string, remotePath string, fileH at.ClientID = clientID at.FileName = remotePath at.FilePathHash = fileHash + at.ContentHash = "content_hash" at.RefType = fileref.FILE timestamp := int64(common.Now()) at.Expiration = timestamp + 7776000 @@ -1654,8 +1655,14 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) remotePath := "/file.txt" - formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + formWriter.WriteField("path_hash", pathHash) formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, pathHash, client.GetClientID(), sch.GetPublicKey()) + if err != nil { + t.Fatal(err) + } + formWriter.WriteField("auth_token", authTicket) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1717,11 +1724,12 @@ func TestHandlers_Requiring_Signature(t *testing.T) { ) filePathHash := fileref.GetReferenceLookup(alloc.Tx, "/file.txt") + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). WithArgs(alloc.ID, filePathHash). WillReturnRows( sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "abcd", "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio="), + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", "qCj3sXXeXUAByi1ERIbcfXzWN75dyocYzyRXnkStXio="), ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) FROM "collaborators" WHERE`)). @@ -1738,7 +1746,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { aa := sqlmock.AnyArg() mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa). WillReturnResult(sqlmock.NewResult(0, 0)) mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "marketplace_share_info" WHERE`)). @@ -1765,8 +1773,14 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) remotePath := "/file.txt" - formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) + pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) + formWriter.WriteField("path_hash", pathHash) formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, pathHash, client.GetClientID(), sch.GetPublicKey()) + if err != nil { + t.Fatal(err) + } + formWriter.WriteField("auth_token", authTicket) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1846,7 +1860,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { WithArgs(alloc.ID, filePathHash). WillReturnRows( sqlmock.NewRows([]string{"path", "type", "path_hash", "lookup_hash", "content_hash", "encrypted_key"}). - AddRow("/file.txt", "f", filePathHash, filePathHash, "abcd", encscheme.GetEncryptedKey()), + AddRow("/file.txt", "f", filePathHash, filePathHash, "content_hash", encscheme.GetEncryptedKey()), ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(1) FROM "collaborators" WHERE`)). @@ -1863,7 +1877,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { aa := sqlmock.AnyArg() mock.ExpectExec(`UPDATE "read_markers"`). - WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa). + WithArgs(client.GetClientPublicKey(), alloc.ID, alloc.OwnerID, aa, aa, aa, aa, aa, aa, aa). WillReturnResult(sqlmock.NewResult(0, 0)) reEncryptionKey, _ := encscheme.GetReGenKey(encscheme.GetEncryptedKey(), "filetype:audio") diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 3cf868072..725c9efa5 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -1,6 +1,7 @@ package handler import ( + "0chain.net/blobbercore/util" "bytes" "context" "encoding/hex" @@ -290,6 +291,8 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "error getting file attributes: %v", err) } + var authToken *readmarker.AuthTicket = nil + if (allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID && !isACollaborator) || len(authTokenString) > 0 { @@ -307,7 +310,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "could not verify the auth ticket") } - var authToken = &readmarker.AuthTicket{} + authToken = &readmarker.AuthTicket{} err = json.Unmarshal([]byte(authTokenString), &authToken) if err != nil { return nil, common.NewErrorf("download_file", @@ -319,6 +322,20 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( return nil, errors.New("content hash does not match the requested file content hash") } + if authToken.RefType == zfileref.DIRECTORY { + hashes := util.GetParentPathHashes(allocationTx, fileref.Path) + found := false + for _, hash := range hashes { + if hash == authToken.FilePathHash { + found = true + break + } + } + if !found { + return nil, errors.New("auth ticket is not authorized to download file specified") + } + } + // if --rx_pay used 3rd_party pays if rxPay { clientIDForReadRedeem = clientID @@ -411,12 +428,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( response.Success = true response.LatestRM = readMarker if len(fileref.EncryptedKey) > 0 { + if authToken == nil { + return nil, errors.New("auth ticket is required to download encrypted file") + } // check if client is authorized to download - shareInfo, err := reference.GetShareInfoRecursive( + shareInfo, err := reference.GetShareInfo( ctx, readMarker.ClientID, - allocationTx, - fileref.Path, + authToken.FilePathHash, ) if err != nil { return nil, errors.New("error during share info lookup in database" + err.Error()) diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index ae2d93ef8..d65087d78 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -3,9 +3,7 @@ package reference import ( "0chain.net/blobbercore/datastore" "context" - "github.com/0chain/gosdk/zboxcore/fileref" "gorm.io/gorm" - "strings" "time" ) @@ -73,21 +71,3 @@ func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*S return shareInfo, nil } -func GetShareInfoRecursive(ctx context.Context, clientID string, allocationTx string, filePath string) (*ShareInfo, error) { - splitted := strings.Split(filePath, "/") - for i := 0; i < len(splitted); i++ { - path := strings.Join(splitted[:len(splitted) - i], "/") - if path == "" { - path = "/" - } - pathHash := fileref.GetReferenceLookup(allocationTx, path) - shareInfo, err := GetShareInfo(ctx, clientID, pathHash) - if err != nil { - return nil, err - } - if shareInfo != nil { - return shareInfo, nil - } - } - return nil, nil -} diff --git a/code/go/0chain.net/blobbercore/util/json.go b/code/go/0chain.net/blobbercore/util/json.go index d35570024..672eddfca 100644 --- a/code/go/0chain.net/blobbercore/util/json.go +++ b/code/go/0chain.net/blobbercore/util/json.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "github.com/0chain/gosdk/zboxcore/fileref" "reflect" "strings" ) @@ -25,3 +26,18 @@ func UnmarshalValidation(v interface{}) error { return nil } + +func GetParentPathHashes(allocationTx string, filePath string) []string { + splitted := strings.Split(filePath, "/") + pathHashes := []string{} + + for i := 0; i < len(splitted); i++ { + path := strings.Join(splitted[:len(splitted) - i], "/") + if path == "" { + path = "/" + } + pathHash := fileref.GetReferenceLookup(allocationTx, path) + pathHashes = append(pathHashes, pathHash) + } + return pathHashes +} From b8294b26018f8d676ca9d2c14c702f1c2fb951f1 Mon Sep 17 00:00:00 2001 From: Uk Date: Sat, 3 Jul 2021 17:48:47 +0545 Subject: [PATCH 53/66] Add revoke flag, add indexes to reference objects --- .../0chain.net/blobbercore/handler/handler.go | 5 ++--- .../blobbercore/handler/handler_test.go | 19 ++++++++----------- .../handler/object_operation_handler.go | 4 ++-- .../blobbercore/reference/shareinfo.go | 15 ++++++++++++--- sql/14-add-marketplace-table.sql | 1 + sql/15-add-indexes-to-reference-objects.sql | 4 ++++ 6 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 sql/15-add-indexes-to-reference-objects.sql diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 4b75ea34b..723e8ed95 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -418,12 +418,12 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { ExpiryAt: common.ToTime(authTicket.Expiration), } - existing, err := reference.GetShareInfo(ctx, authTicket.ClientID, authTicket.FilePathHash) + existingShare, err := reference.GetShareInfo(ctx, authTicket.ClientID, authTicket.FilePathHash) if err != nil { return nil, err } - if existing != nil { + if existingShare != nil { err = reference.UpdateShareInfo(ctx, shareInfo) } else { err = reference.AddShareInfo(ctx, shareInfo) @@ -434,7 +434,6 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { resp := map[string]interface{} { "message": "Share info added successfully", - "existing": existing, } return resp, nil diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index d055f1007..a2a486a18 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -32,7 +32,6 @@ import ( "net/http/httptest" "os" "regexp" - "strings" "testing" "time" ) @@ -1193,11 +1192,11 @@ func TestHandlers_Requiring_Signature(t *testing.T) { aa := sqlmock.AnyArg() mock.ExpectExec(`INSERT INTO "marketplace_share_info"`). - WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "regenkey", aa, aa). + WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "regenkey", aa, false, aa). WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, - wantBody: "{\"message\":\"Share info added successfully\",\"existing\":false}\n\n", + wantBody: "{\"message\":\"Share info added successfully\"}\n", }, { name: "UpdateShareInfo", @@ -1288,7 +1287,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, - wantBody: "{\"message\":\"Share info added successfully\",\"existing\":true}\n\n", + wantBody: "{\"message\":\"Share info added successfully\"}\n", }, { name: "RevokeShareInfo_OK_Existing_Share", @@ -1362,8 +1361,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { AddRow("/file.txt", filePathHash), ) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "marketplace_share_info" WHERE`)). - WithArgs("abcdefgh", filePathHash). + mock.ExpectExec(regexp.QuoteMeta(`UPDATE "marketplace_share_info"`)). + WithArgs(true, "abcdefgh", filePathHash). WillReturnResult(sqlmock.NewResult(0, 1)) }, @@ -1442,8 +1441,9 @@ func TestHandlers_Requiring_Signature(t *testing.T) { AddRow("/file.txt", filePathHash), ) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM "marketplace_share_info" WHERE`)). - WithArgs("abcdefgh", filePathHash).WillReturnError(gorm.ErrRecordNotFound) + mock.ExpectExec(regexp.QuoteMeta(`UPDATE "marketplace_share_info"`)). + WithArgs(true, "abcdefgh", filePathHash). + WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, @@ -1896,9 +1896,6 @@ func TestHandlers_Requiring_Signature(t *testing.T) { } tests := append(positiveTests, negativeTests...) for _, test := range tests { - if !strings.Contains(test.name, "Download") { - continue - } t.Run(test.name, func(t *testing.T) { mock := datastore.MockTheStore(t) test.setupDbMock(mock) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 725c9efa5..d7fb4eba1 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -437,9 +437,9 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( readMarker.ClientID, authToken.FilePathHash, ) - if err != nil { + if err != nil { return nil, errors.New("error during share info lookup in database" + err.Error()) - } else if shareInfo == nil { + } else if shareInfo == nil || shareInfo.Revoked { return nil, errors.New("client does not have permission to download the file. share does not exist") } diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index d65087d78..0fb4c606f 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -14,6 +14,7 @@ type ShareInfo struct { FilePathHash string `gorm:"file_path_hash" json:"file_path_hash,omitempty"` ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` + Revoked bool `gorm:"revoked" json:"revoked,omitempty"` ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` } @@ -21,6 +22,7 @@ func TableName() string { return "marketplace_share_info" } +// add share if it already doesnot exist func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) return db.Table(TableName()).Create(shareInfo).Error @@ -28,14 +30,21 @@ func AddShareInfo(ctx context.Context, shareInfo ShareInfo) error { func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) + result := db.Table(TableName()). - Where("client_id = ?", shareInfo.ClientID). - Where("file_path_hash = ?", shareInfo.FilePathHash). - Delete(&ShareInfo{}) + Where(&ShareInfo{ + ClientID: shareInfo.ClientID, + FilePathHash: shareInfo.FilePathHash, + Revoked: false, + }). + Updates(ShareInfo{ + Revoked: true, + }) if result.Error != nil { return result.Error } + if result.RowsAffected == 0 { return gorm.ErrRecordNotFound } diff --git a/sql/14-add-marketplace-table.sql b/sql/14-add-marketplace-table.sql index 26dd4cfde..bbfd4b043 100644 --- a/sql/14-add-marketplace-table.sql +++ b/sql/14-add-marketplace-table.sql @@ -9,6 +9,7 @@ CREATE TABLE marketplace_share_info ( re_encryption_key TEXT NOT NULL, client_encryption_public_key TEXT NOT NULL, expiry_at TIMESTAMP NULL, + revoked BOOLEAN NOT NULL DEFAULT false, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW() ); diff --git a/sql/15-add-indexes-to-reference-objects.sql b/sql/15-add-indexes-to-reference-objects.sql new file mode 100644 index 000000000..dc8146e84 --- /dev/null +++ b/sql/15-add-indexes-to-reference-objects.sql @@ -0,0 +1,4 @@ +\connect blobber_meta; + +CREATE INDEX idx_reference_objects_for_lookup_hash ON reference_objects(allocation_id, lookup_hash); +CREATE INDEX idx_reference_objects_for_path ON reference_objects(allocation_id, path); From 78e5d4501dd9644789fc271bee2a4e8ff1ec2f09 Mon Sep 17 00:00:00 2001 From: Uk Date: Sat, 3 Jul 2021 18:34:57 +0545 Subject: [PATCH 54/66] Fix logic for update --- code/go/0chain.net/blobbercore/reference/shareinfo.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 0fb4c606f..33f83dcc0 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -53,12 +53,14 @@ func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) + dupShareInfo := shareInfo + dupShareInfo.Revoked = false return db.Table(TableName()). Where(&ShareInfo{ ClientID: shareInfo.ClientID, FilePathHash: shareInfo.FilePathHash, }). - Updates(shareInfo).Error + Updates(dupShareInfo).Error } func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*ShareInfo, error) { From af331c1354462eeba19a40123cb21f91b202c8af Mon Sep 17 00:00:00 2001 From: Uk Date: Sat, 3 Jul 2021 19:25:59 +0545 Subject: [PATCH 55/66] Updates to zero value update --- code/go/0chain.net/blobbercore/handler/handler_test.go | 4 ++-- code/go/0chain.net/blobbercore/reference/shareinfo.go | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index a2a486a18..e077ae950 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -1283,8 +1283,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { aa := sqlmock.AnyArg() mock.ExpectExec(`UPDATE "marketplace_share_info"`). - WithArgs(client.GetClientID(), "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c", "regenkey", aa, aa, "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). - WillReturnResult(sqlmock.NewResult(0, 0)) + WithArgs("regenkey", "kkk", false, aa, "abcdefgh", "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c"). + WillReturnResult(sqlmock.NewResult(0, 1)) }, wantCode: http.StatusOK, wantBody: "{\"message\":\"Share info added successfully\"}\n", diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index 33f83dcc0..c006c5b33 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -14,7 +14,7 @@ type ShareInfo struct { FilePathHash string `gorm:"file_path_hash" json:"file_path_hash,omitempty"` ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` - Revoked bool `gorm:"revoked" json:"revoked,omitempty"` + Revoked bool `gorm:"revoked" json:"revoked"` ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` } @@ -53,14 +53,15 @@ func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { db := datastore.GetStore().GetTransaction(ctx) - dupShareInfo := shareInfo - dupShareInfo.Revoked = false + return db.Table(TableName()). Where(&ShareInfo{ ClientID: shareInfo.ClientID, FilePathHash: shareInfo.FilePathHash, }). - Updates(dupShareInfo).Error + Select("Revoked", "ReEncryptionKey", "ExpiryAt", "ClientEncryptionPublicKey"). + Updates(shareInfo). + Error } func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*ShareInfo, error) { From 73ddb9c9fb93b4d1fdfbb7930e607a850a6a3608 Mon Sep 17 00:00:00 2001 From: Uk Date: Mon, 5 Jul 2021 20:36:43 +0545 Subject: [PATCH 56/66] Add uml diagram for share and share revoke --- docs/src/share.plantuml | 17 +++++++++++++++++ docs/src/share_revoke.plantuml | 13 +++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 docs/src/share.plantuml create mode 100644 docs/src/share_revoke.plantuml diff --git a/docs/src/share.plantuml b/docs/src/share.plantuml new file mode 100644 index 000000000..08623795e --- /dev/null +++ b/docs/src/share.plantuml @@ -0,0 +1,17 @@ +@startuml + +title Buyer shares encrypted file +actor Seller +actor Buyer + +Buyer -> Seller: Client Id, encryption public key + +Seller -> Seller : Generate auth ticket for file / folder using \n\ +buyer client id as well as encryption public key +Seller --> Blobber : Upload auth ticket +Blobber --> Blobber : Save auth ticket to **marketplace_share_info** \n\ +table +Seller --> Seller : Check upload consensus status +Seller --> Seller : Return auth ticket + +@enduml diff --git a/docs/src/share_revoke.plantuml b/docs/src/share_revoke.plantuml new file mode 100644 index 000000000..739347a37 --- /dev/null +++ b/docs/src/share_revoke.plantuml @@ -0,0 +1,13 @@ +@startuml + +title Buyer revokes share for client +actor Seller + +Seller -> Seller : Revoke share using **client id**,\n\ +**allocation id** and **remote path** +Seller --> Blobber : Revoke share using provided parameters +Blobber --> Blobber : Find path share in **marketplace_share_info** \n\ +table and set **revoked** flag to true +Seller --> Seller : return consensus status, \n\ +we need 100% consensus for success +@enduml From 22db299b7b5334ccf5584b6df1680a5122f6dc44 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 6 Jul 2021 19:07:51 +0545 Subject: [PATCH 57/66] Merge --- .github/workflows/build.yml | 86 ++++ .github/workflows/ci.yml | 3 +- .github/workflows/cicd.yml | 108 +++++ .github/workflows/cicd_hetz.yml | 166 ++++++++ .github/workflows/test.yml | 10 + README.md | 7 + code/go/0chain.net/blobber/main.go | 388 +++++++++--------- .../blobbercore/allocation/entity.go | 16 +- .../blobbercore/allocation/newfilechange.go | 9 + .../blobbercore/allocation/protocol.go | 3 +- .../blobbercore/blobbergrpc/blobber.pb.go | 98 +++-- .../blobbergrpc/proto/blobber.proto | 23 +- .../blobbercore/challenge/entity.go | 1 + .../blobbercore/challenge/worker.go | 7 +- .../0chain.net/blobbercore/config/config.go | 18 + .../blobbercore/filestore/chunk_writer.go | 115 ++++++ .../filestore/chunk_writer_test.go | 93 +++++ .../blobbercore/filestore/fs_store.go | 63 ++- .../0chain.net/blobbercore/filestore/store.go | 14 + .../0chain.net/blobbercore/handler/convert.go | 3 +- code/go/0chain.net/blobbercore/handler/dto.go | 5 + .../blobbercore/handler/grpc_handler.go | 92 ++--- .../0chain.net/blobbercore/handler/helper.go | 9 +- .../handler/object_operation_handler.go | 141 ++++--- .../blobbercore/handler/protocol.go | 136 +++--- .../blobbercore/handler/storage_handler.go | 36 +- .../0chain.net/blobbercore/handler/zcncore.go | 40 +- .../blobbercore/openapi/blobber.swagger.json | 9 +- .../blobbercore/readmarker/entity.go | 1 - .../blobbercore/stats/blobberstats.go | 11 +- .../0chain.net/blobbercore/stats/handler.go | 2 +- code/go/0chain.net/core/common/handler.go | 2 + code/go/0chain.net/core/encryption/keys.go | 59 +++ .../0chain.net/core/encryption/keys_test.go | 56 +++ code/go/0chain.net/core/transaction/entity.go | 20 +- code/go/0chain.net/go.mod | 1 + .../validatorcore/storage/context.go | 3 +- config/0chain_blobber.yaml | 4 + docker.local/b0docker-compose.yml | 2 +- .../bin/build.blobber-integration-tests.sh | 17 +- docker.local/bin/build.blobber.sh | 17 +- docs/cicd/CICD_GITACTIONS.md | 128 ++++++ docs/cicd/blobber.png | Bin 0 -> 54031 bytes docs/src/repair.plantuml | 2 +- sql/14-increase_owner_pubkey.sql | 16 + sql/15-add-allocation-columns.sql | 6 + 46 files changed, 1541 insertions(+), 505 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/cicd.yml create mode 100644 .github/workflows/cicd_hetz.yml create mode 100644 .github/workflows/test.yml create mode 100644 code/go/0chain.net/blobbercore/filestore/chunk_writer.go create mode 100644 code/go/0chain.net/blobbercore/filestore/chunk_writer_test.go create mode 100644 docs/cicd/CICD_GITACTIONS.md create mode 100644 docs/cicd/blobber.png create mode 100644 sql/14-increase_owner_pubkey.sql create mode 100644 sql/15-add-allocation-columns.sql diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..c9ceeb49e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,86 @@ +name: Dockerize + +on: + workflow_dispatch: + inputs: + latest_tag: + description: 'type yes for building latest tag' + default: 'no' + required: true + +env: + ZCHAIN_BUILDBASE: zchain_build_base + ZCHAIN_BUILDRUN: zchain_run_base + BLOBBER_REGISTRY: ${{ secrets.BLOBBER_REGISTRY }} + VALIDATOR_REGISTRY: ${{ secrets.VALIDATOR_REGISTRY }} + +jobs: + dockerize_blobber: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Get the version + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build blobber + run: | + docker build -t $BLOBBER_REGISTRY:$TAG -f "$DOCKERFILE_BLOB" . + docker tag $BLOBBER_REGISTRY:$TAG $BLOBBER_REGISTRY:latest + docker push $BLOBBER_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_BLOB: "docker.local/Dockerfile" + + - name: Push blobber + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $BLOBBER_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} + + dockerize_validator: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v1 + + - name: Get the version + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build validator + run: | + docker build -t $VALIDATOR_REGISTRY:$TAG -f "$DOCKERFILE_PROXY" . + docker tag $VALIDATOR_REGISTRY:$TAG $VALIDATOR_REGISTRY:latest + docker push $VALIDATOR_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_PROXY: "docker.local/ValidatorDockerfile" + + - name: Push validator + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $VALIDATOR_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fc27a3d3..8b53fa32f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,6 @@ jobs: run: | docker network create --driver=bridge --subnet=198.18.0.0/15 --gateway=198.18.0.255 testnet0 ./docker.local/bin/build.blobber.sh - test: runs-on: ubuntu-20.04 steps: @@ -128,4 +127,4 @@ jobs: docker build -t $VALIDATOR_REGISTRY:$TAG -f docker.local/ValidatorDockerfile . docker push $VALIDATOR_REGISTRY:$TAG env: - TAG: ${{ steps.get_version.outputs.VERSION }} + TAG: ${{ steps.get_version.outputs.VERSION }} \ No newline at end of file diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml new file mode 100644 index 000000000..abb166577 --- /dev/null +++ b/.github/workflows/cicd.yml @@ -0,0 +1,108 @@ +name: CICD_TEST_HERTZNER + +on: + push: + branches: + - gitactionsfix + +env: + GITHUB_TOKEN: ${{ secrets.CICD }} + +jobs: + build-push-image: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Get Branch + id: get_branch + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + echo ::set-output name=BRANCH::${BRANCH} + echo "This workflow run is triggered by ${{ github.event_name }} ." + + - name: Triggering build.yml for creating & pushing docker images. + uses: convictional/trigger-workflow-and-wait@v1.3.0 + with: + owner: 0chain + repo: blobber + github_token: ${{ secrets.CICD }} + workflow_file_name: test.yml + ref: ${{ steps.get_branch.outputs.BRANCH }} + # inputs: '{"DOCKERHUB_REPO":"TEST"}' + propagate_failure: true + trigger_workflow: true + wait_workflow: true + + # network-setup: + # runs-on: ubuntu-20.04 + # env: + # HOST: testnet.load.testnet-0chain.net + # steps: + # - uses: actions/checkout@v2 + + # - uses: azure/setup-helm@v1 + # with: + # version: 'v3.2.2' + # - name: Setup helm repo + # run: | + # helm repo add 0chain http://0chain-helm-chart.s3-website.us-east-2.amazonaws.com/ + # - name: Setup kubeconfig + # run: | + # mkdir -p ~/.kube + # echo "${{ secrets.KUBECONFIG64TEST }}" | base64 -d > ~/.kube/config + # - name: Uninstall old release + # run: | + # helm uninstall zerochain -n zerochain || true + # - name: Setup chain + # run: | + # helm install zerochain -n zerochain \ + # --set ingress.host=${HOST} \ + # --set sharder.image.tag=latest \ + # --set miner.image.tag=latest \ + # --set blobber.image.tag=latest \ + # --set validator.image.tag=latest \ + # 0chain/0chain + # - name: Check if services are up + # run: | + # printf 'Waiting for 0dns' + # until [[ $(curl -I --silent -o /dev/null -w %{http_code} http://${HOST}/dns/) =~ 2[0-9][0-9] ]] ;do + # printf '.' + # sleep 2 + # done + # printf 'Waiting for 1st sharder' + # until [[ $(curl -I --silent -o /dev/null -w %{http_code} http://${HOST}/sharder0/) =~ 2[0-9][0-9] ]] ;do + # printf '.' + # sleep 2 + # done + # printf 'Waiting for 1st miner' + # until [[ $(curl -I --silent -o /dev/null -w %{http_code} http://${HOST}/miner0/) =~ 2[0-9][0-9] ]] ;do + # printf '.' + # sleep 2 + # done + + # # - name: Triggering ci.yml to run Postman API tests + # # uses: convictional/trigger-workflow-and-wait@v1.3.0 + # # with: + # # owner: 0chain + # # repo: 0proxy + # # github_token: ${{ secrets.GOSDK }} + # # workflow_file_name: build.yml + # # ref: master + # # propagate_failure: true + # # trigger_workflow: true + # # wait_workflow: true + + # # - name: Build + # # run: make build + # # - name: Running Load Tests + # # run: | + # # make run config=loadTest-load-testnet.yaml + + # - name: Uninstall the release + # if: ${{ always() }} + # run: helm uninstall zerochain -n zerochain + + # - name: Deleting kubeconfig + # run: | + # rm -rf ~/.kube diff --git a/.github/workflows/cicd_hetz.yml b/.github/workflows/cicd_hetz.yml new file mode 100644 index 000000000..47a16e435 --- /dev/null +++ b/.github/workflows/cicd_hetz.yml @@ -0,0 +1,166 @@ +name: CICD_Hetzner + +# on: +# push: +# branches: +# - gitactionsfix + +on: + workflow_dispatch: + inputs: + latest_tag: + description: 'type yes for building latest tag' + default: 'no' + required: true + +env: + ZCHAIN_BUILDBASE: zchain_build_base + ZCHAIN_BUILDRUN: zchain_run_base + BLOBBER_REGISTRY: ${{ secrets.BLOBBER_REGISTRY_TEST }} + VALIDATOR_REGISTRY: ${{ secrets.VALIDATOR_REGISTRY_TEST }} + KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA_TEST }} + KUBE_NAMESPACE: test + +jobs: + dockerize_blobber: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Get the Version for Tagging + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build & Push Blobber Image + run: | + docker build -t $BLOBBER_REGISTRY:$TAG -f "$DOCKERFILE_BLOB" . + docker tag $BLOBBER_REGISTRY:$TAG $BLOBBER_REGISTRY:latest + # docker push $BLOBBER_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_BLOB: "docker.local/Dockerfile" + + - name: Push Blobber Image with Latest TAG + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $BLOBBER_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} + + - name: Update Blobber Image to Kubernetes Cluster + uses: kodermax/kubectl-aws-eks@master + with: + args: set image deployment/blobber-01 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/blobber-02 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/blobber-03 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/blobber-04 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/blobber-05 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/blobber-06 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + + - name: Verify Kubernetes Deployment + uses: kodermax/kubectl-aws-eks@master + with: + args: rollout status blobber-01 my-pod -n $KUBE_NAMESPACE + args: rollout status blobber-02 my-pod -n $KUBE_NAMESPACE + args: rollout status blobber-03 my-pod -n $KUBE_NAMESPACE + args: rollout status blobber-04 my-pod -n $KUBE_NAMESPACE + args: rollout status blobber-05 my-pod -n $KUBE_NAMESPACE + args: rollout status blobber-06 my-pod -n $KUBE_NAMESPACE + + # - name: Triggering LoadTest Repo build + # uses: convictional/trigger-workflow-and-wait@v1.3.0 + # with: + # owner: 0chain + # repo: loadTest + # github_token: ${{ secrets.GOSDK }} + # workflow_file_name: load-test-v1.yml + # ref: master + # inputs: '{}' + # propagate_failure: true + # trigger_workflow: true + # wait_workflow: true + + + dockerize_validator: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v1 + + - name: Get the Version for Tagging + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} + + - name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build & Push Validator Image + run: | + docker build -t $VALIDATOR_REGISTRY:$TAG -f "$DOCKERFILE_PROXY" . + docker tag $VALIDATOR_REGISTRY:$TAG $VALIDATOR_REGISTRY:latest + # docker push $VALIDATOR_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_PROXY: "docker.local/ValidatorDockerfile" + + - name: Push Validator Image with Latest TAG + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $VALIDATOR_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} + + - name: Update Validator Image to Kubernetes Cluster + uses: kodermax/kubectl-aws-eks@master + with: + args: set image deployment/validator-01 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/validator-02 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/validator-03 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/validator-04 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/validator-05 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + args: set image deployment/validator-06 app=$BLOBBER_REGISTRY:$TAG --record -n $KUBE_NAMESPACE + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + + - name: Verify Kubernetes Deployment + uses: kodermax/kubectl-aws-eks@master + with: + args: rollout status validator-01 -n $KUBE_NAMESPACE + args: rollout status validator-02 -n $KUBE_NAMESPACE + args: rollout status validator-03 -n $KUBE_NAMESPACE + args: rollout status validator-04 -n $KUBE_NAMESPACE + args: rollout status validator-05 -n $KUBE_NAMESPACE + args: rollout status validator-06 -n $KUBE_NAMESPACE + + # - name: Triggering LoadTest Repo build + # uses: convictional/trigger-workflow-and-wait@v1.3.0 + # with: + # owner: 0chain + # repo: loadTest + # github_token: ${{ secrets.GOSDK }} + # workflow_file_name: load-test-v1.yml + # ref: master + # inputs: '{}' + # propagate_failure: true + # trigger_workflow: true + # wait_workflow: true \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..bb984e3a6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,10 @@ +name: test + +on: + workflow_dispatch + +jobs: + dockerize_blobber: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 diff --git a/README.md b/README.md index c1630c531..f55f8a205 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,13 @@ To link to local gosdk so that the changes are reflected on the blobber build pl $ ./docker.local/bin/build.blobber.dev.sh ``` +For Apple M1 chip builds: + +``` + +$ ./docker.local/bin/build.blobber.sh -m1 + +``` 3. After building the container for blobber, go to Blobber1 directory (git/blobber/docker.local/blobber1) and run the container using diff --git a/code/go/0chain.net/blobber/main.go b/code/go/0chain.net/blobber/main.go index 322c27232..2a1944721 100644 --- a/code/go/0chain.net/blobber/main.go +++ b/code/go/0chain.net/blobber/main.go @@ -8,13 +8,16 @@ import ( "log" "net" "net/http" - "net/url" "os" "runtime" "strconv" - "strings" "time" + "go.uber.org/zap" + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/spf13/viper" + "github.com/0chain/gosdk/zcncore" "0chain.net/blobbercore/allocation" "0chain.net/blobbercore/challenge" "0chain.net/blobbercore/config" @@ -27,32 +30,50 @@ import ( "0chain.net/core/chain" "0chain.net/core/common" "0chain.net/core/encryption" + "0chain.net/core/node" "0chain.net/core/logging" . "0chain.net/core/logging" - "0chain.net/core/node" - "0chain.net/core/transaction" - "0chain.net/core/util" - - "github.com/0chain/gosdk/zcncore" - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/spf13/viper" - "go.uber.org/zap" ) -//var BLOBBER_REGISTERED_LOOKUP_KEY = datastore.ToKey("blobber_registration") - var startTime time.Time var serverChain *chain.Chain var filesDir *string var metadataDB *string func initHandlers(r *mux.Router) { - r.HandleFunc("/", HomePageHandler) + r.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { + mc := chain.GetServerChain() + + fmt.Fprintf(w, "
Running since %v ...\n", startTime) + fmt.Fprintf(w, "
Working on the chain: %v
\n", mc.ID) + fmt.Fprintf(w, + "
I am a blobber with
  • id:%v
  • public_key:%v
  • build_tag:%v
\n", + node.Self.ID, node.Self.PublicKey, build.BuildTag, + ) + + fmt.Fprintf(w, "
Miners ...\n") + network := zcncore.GetNetwork() + for _, miner := range network.Miners { + fmt.Fprintf(w, "%v\n", miner) + } + + fmt.Fprintf(w, "
Sharders ...\n") + for _, sharder := range network.Sharders { + fmt.Fprintf(w, "%v\n", sharder) + } + }) + handler.SetupHandlers(r) } -func SetupWorkerConfig() { +var fsStore filestore.FileStore //nolint:unused // global which might be needed somewhere + +func initEntities() (err error) { + fsStore, err = filestore.SetupFSStore(*filesDir + "/files") + return err +} + +func setupWorkerConfig() { config.Configuration.ContentRefWorkerFreq = viper.GetInt64("contentref_cleaner.frequency") config.Configuration.ContentRefWorkerTolerance = viper.GetInt64("contentref_cleaner.tolerance") @@ -117,48 +138,7 @@ func SetupWorkerConfig() { config.Configuration.ServiceCharge = viper.GetFloat64("service_charge") } -func SetupWorkers() { - var root = common.GetRootContext() - handler.SetupWorkers(root) - challenge.SetupWorkers(root) - readmarker.SetupWorkers(root) - writemarker.SetupWorkers(root) - allocation.StartUpdateWorker(root, - config.Configuration.UpdateAllocationsInterval) - // stats.StartEventDispatcher(2) -} - -var fsStore filestore.FileStore //nolint:unused // global which might be needed somewhere - -func initEntities() (err error) { - fsStore, err = filestore.SetupFSStore(*filesDir + "/files") - return err -} - -func initServer() { - -} - -func checkForDBConnection() { - retries := 0 - var err error - for retries < 600 { - err = datastore.GetStore().Open() - if err != nil { - time.Sleep(1 * time.Second) - retries++ - continue - } - break - } - - if err != nil { - Logger.Error("Error in opening the database. Shutting the server down") - panic(err) - } -} - -func processMinioConfig(reader io.Reader) error { +func setupMinioConfig(reader io.Reader) error { scanner := bufio.NewScanner(reader) more := scanner.Scan() if !more { @@ -193,26 +173,154 @@ func processMinioConfig(reader io.Reader) error { return nil } -func isValidOrigin(origin string) bool { - var url, err = url.Parse(origin) +func setupWorkers() { + var root = common.GetRootContext() + handler.SetupWorkers(root) + challenge.SetupWorkers(root) + readmarker.SetupWorkers(root) + writemarker.SetupWorkers(root) + allocation.StartUpdateWorker(root, + config.Configuration.UpdateAllocationsInterval) +} + +func setupDatabase() { + // check for database connection + for i := 600; i > 0; i-- { + time.Sleep(1 * time.Second) + if err := datastore.GetStore().Open(); err == nil { + if i == 1 { // no more attempts + Logger.Error("Failed to connect to the database. Shutting the server down") + panic(err) // fail + } + + return // success + } + } +} + +func setupOnChain() { + const ATTEMPT_DELAY = 60 * 1 // 1 minute + + // setup wallet + if err := handler.WalletRegister(); err != nil { + panic(err) + } + + // setup blobber (add or update) on the blockchain (multiple attempts) + for i := 10; i > 0; i-- { + if err := addOrUpdateOnChain(); err != nil { + if i == 1 { // no more attempts + panic(err) + } + } else { + break + } + + time.Sleep(ATTEMPT_DELAY * time.Second) + } + + setupWorkers() + + go healthCheckOnChainWorker() + + if config.Configuration.PriceInUSD { + go addOrUpdateOnChainWorker() + } +} + +func addOrUpdateOnChain() error { + txnHash, err := handler.BlobberAdd(common.GetRootContext()) + if err != nil { + return err + } + + if t, err := handler.TransactionVerify(txnHash); err != nil { + Logger.Error("Failed to verify blobber add/update transaction", zap.Any("err", err), zap.String("txn.Hash", txnHash)) + } else { + Logger.Info("Verified blobber add/update transaction", zap.String("txn_hash", t.Hash), zap.Any("txn_output", t.TransactionOutput)) + } + + return err +} + +func addOrUpdateOnChainWorker() { + var REPEAT_DELAY = 60 * 60 * time.Duration(viper.GetInt("price_worker_in_hours")) // 12 hours with default settings + for { + time.Sleep(REPEAT_DELAY * time.Second) + if err := addOrUpdateOnChain(); err != nil { + continue // pass // required by linting + } + } +} + +func healthCheckOnChain() error { + txnHash, err := handler.BlobberHealthCheck(common.GetRootContext()) if err != nil { - return false + if err == handler.ErrBlobberHasRemoved { + return nil + } else { + return err + } + } + + if t, err := handler.TransactionVerify(txnHash); err != nil { + Logger.Error("Failed to verify blobber health check", zap.Any("err", err), zap.String("txn.Hash", txnHash)) + } else { + Logger.Info("Verified blobber health check", zap.String("txn_hash", t.Hash), zap.Any("txn_output", t.TransactionOutput)) + } + + return err +} + +func healthCheckOnChainWorker() { + const REPEAT_DELAY = 60 * 15 // 15 minutes + + for { + time.Sleep(REPEAT_DELAY * time.Second) + if err := healthCheckOnChain(); err != nil { + continue // pass // required by linting + } } - var host = url.Hostname() - if host == "localhost" { - return true +} + +func setup(logDir string) error { + // init blockchain related stuff + zcncore.SetLogFile(logDir + "/0chainBlobber.log", false) + zcncore.SetLogLevel(3) + if err := zcncore.InitZCNSDK(serverChain.BlockWorker, config.Configuration.SignatureScheme); err != nil { + return err } - if host == "0chain.net" || host == "0box.io" || - strings.HasSuffix(host, ".0chain.net") || - strings.HasSuffix(host, ".alphanet-0chain.net") || - strings.HasSuffix(host, ".testnet-0chain.net") || - strings.HasSuffix(host, ".devnet-0chain.net") || - strings.HasSuffix(host, ".mainnet-0chain.net") { - return true + if err := zcncore.SetWalletInfo(node.Self.GetWalletString(), false); err != nil { + return err } - return false + + // setup on blockchain + go setupOnChain() + return nil } +// // Comment out to pass lint. Still keep this function around in case we want to +// // change how CORS validates origins. +// func isValidOrigin(origin string) bool { +// var url, err = url.Parse(origin) +// if err != nil { +// return false +// } +// var host = url.Hostname() +// if host == "localhost" { +// return true +// } +// if host == "0chain.net" || host == "0box.io" || +// strings.HasSuffix(host, ".0chain.net") || +// strings.HasSuffix(host, ".alphanet-0chain.net") || +// strings.HasSuffix(host, ".testnet-0chain.net") || +// strings.HasSuffix(host, ".devnet-0chain.net") || +// strings.HasSuffix(host, ".mainnet-0chain.net") { +// return true +// } +// return false +// } + func main() { deploymentMode := flag.Int("deployment_mode", 2, "deployment_mode") keysFile := flag.String("keys_file", "", "keys_file") @@ -238,7 +346,7 @@ func main() { } config.Configuration.ChainID = viper.GetString("server_chain.id") config.Configuration.SignatureScheme = viper.GetString("server_chain.signature_scheme") - SetupWorkerConfig() + setupWorkerConfig() if *filesDir == "" { panic("Please specify --files_dir absolute folder name option where uploaded files can be stored") @@ -273,7 +381,7 @@ func main() { panic(err) } - err = processMinioConfig(reader) + err = setupMinioConfig(reader) if err != nil { panic(err) } @@ -309,13 +417,13 @@ func main() { chain.SetServerChain(serverChain) - checkForDBConnection() + setupDatabase() // Initialize after server chain is setup. if err := initEntities(); err != nil { Logger.Error("Error setting up blobber on blockchian" + err.Error()) } - if err := SetupBlobberOnBC(*logDir); err != nil { + if err := setup(*logDir); err != nil { Logger.Error("Error setting up blobber on blockchian" + err.Error()) } mode := "main net" @@ -334,14 +442,18 @@ func main() { headersOk := handlers.AllowedHeaders([]string{ "X-Requested-With", "X-App-Client-ID", "X-App-Client-Key", "Content-Type", + "X-App-Client-Signature", }) - originsOk := handlers.AllowedOriginValidator(isValidOrigin) + + // Allow anybody to access API. + // originsOk := handlers.AllowedOriginValidator(isValidOrigin) + originsOk := handlers.AllowedOrigins([]string{"*"}) + methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"}) rl := common.ConfigRateLimits() initHandlers(r) - initServer() grpcServer := handler.NewServerWithMiddlewares(rl) handler.RegisterGRPCServices(r, grpcServer) @@ -379,125 +491,3 @@ func main() { }(*grpcPortString) log.Fatal(server.ListenAndServe()) } - -func RegisterBlobber() { - - registrationRetries := 0 - //ctx := badgerdbstore.GetStorageProvider().WithConnection(common.GetRootContext()) - for registrationRetries < 10 { - txnHash, err := handler.RegisterBlobber(common.GetRootContext()) - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - txnVerified := false - verifyRetries := 0 - for verifyRetries < util.MAX_RETRIES { - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - t, err := transaction.VerifyTransaction(txnHash, chain.GetServerChain()) - if err == nil { - Logger.Info("Transaction for adding blobber accepted and verified", zap.String("txn_hash", t.Hash), zap.Any("txn_output", t.TransactionOutput)) - //badgerdbstore.GetStorageProvider().WriteBytes(ctx, BLOBBER_REGISTERED_LOOKUP_KEY, []byte(txnHash)) - //badgerdbstore.GetStorageProvider().Commit(ctx) - SetupWorkers() - go BlobberHealthCheck() - if config.Configuration.PriceInUSD { - go UpdateBlobberSettings() - } - return - } - verifyRetries++ - } - - if !txnVerified { - Logger.Error("Add blobber transaction could not be verified", zap.Any("err", err), zap.String("txn.Hash", txnHash)) - } - } -} - -func BlobberHealthCheck() { - const HEALTH_CHECK_TIMER = 60 * 15 // 15 Minutes - for { - txnHash, err := handler.BlobberHealthCheck(common.GetRootContext()) - if err != nil && err == handler.ErrBlobberHasRemoved { - time.Sleep(HEALTH_CHECK_TIMER * time.Second) - continue - } - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - txnVerified := false - verifyRetries := 0 - for verifyRetries < util.MAX_RETRIES { - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - t, err := transaction.VerifyTransaction(txnHash, chain.GetServerChain()) - if err == nil { - txnVerified = true - Logger.Info("Transaction for blobber health check verified", zap.String("txn_hash", t.Hash), zap.Any("txn_output", t.TransactionOutput)) - break - } - verifyRetries++ - } - - if !txnVerified { - Logger.Error("Blobber health check transaction could not be verified", zap.Any("err", err), zap.String("txn.Hash", txnHash)) - } - time.Sleep(HEALTH_CHECK_TIMER * time.Second) - } -} - -func UpdateBlobberSettings() { - var UPDATE_SETTINGS_TIMER = 60 * 60 * time.Duration(viper.GetInt("price_worker_in_hours")) - time.Sleep(UPDATE_SETTINGS_TIMER * time.Second) - for { - txnHash, err := handler.UpdateBlobberSettings(common.GetRootContext()) - if err != nil { - time.Sleep(UPDATE_SETTINGS_TIMER * time.Second) - continue - } - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - txnVerified := false - verifyRetries := 0 - for verifyRetries < util.MAX_RETRIES { - time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - t, err := transaction.VerifyTransaction(txnHash, chain.GetServerChain()) - if err == nil { - txnVerified = true - Logger.Info("Transaction for blobber update settings verified", zap.String("txn_hash", t.Hash), zap.Any("txn_output", t.TransactionOutput)) - break - } - verifyRetries++ - } - - if !txnVerified { - Logger.Error("Blobber update settings transaction could not be verified", zap.Any("err", err), zap.String("txn.Hash", txnHash)) - } - time.Sleep(UPDATE_SETTINGS_TIMER * time.Second) - } -} - -func SetupBlobberOnBC(logDir string) error { - var logName = logDir + "/0chainBlobber.log" - zcncore.SetLogFile(logName, false) - zcncore.SetLogLevel(3) - if err := zcncore.InitZCNSDK(serverChain.BlockWorker, config.Configuration.SignatureScheme); err != nil { - return err - } - if err := zcncore.SetWalletInfo(node.Self.GetWalletString(), false); err != nil { - return err - } - go RegisterBlobber() - return nil -} - -/*HomePageHandler - provides basic info when accessing the home page of the server */ -func HomePageHandler(w http.ResponseWriter, r *http.Request) { - mc := chain.GetServerChain() - fmt.Fprintf(w, "
Running since %v ...\n", startTime) - fmt.Fprintf(w, "
Working on the chain: %v
\n", mc.ID) - fmt.Fprintf(w, "
I am a blobber with
  • id:%v
  • public_key:%v
  • build_tag:%v
\n", node.Self.ID, node.Self.PublicKey, build.BuildTag) - fmt.Fprintf(w, "
Miners ...\n") - network := zcncore.GetNetwork() - for _, miner := range network.Miners { - fmt.Fprintf(w, "%v\n", miner) - } - fmt.Fprintf(w, "
Sharders ...\n") - for _, sharder := range network.Sharders { - fmt.Fprintf(w, "%v\n", sharder) - } -} diff --git a/code/go/0chain.net/blobbercore/allocation/entity.go b/code/go/0chain.net/blobbercore/allocation/entity.go index 62a11125a..3070b4d8d 100644 --- a/code/go/0chain.net/blobbercore/allocation/entity.go +++ b/code/go/0chain.net/blobbercore/allocation/entity.go @@ -25,6 +25,8 @@ type Allocation struct { UsedSize int64 `gorm:"column:used_size"` OwnerID string `gorm:"column:owner_id"` OwnerPublicKey string `gorm:"column:owner_public_key"` + RepairerID string `gorm:"column:repairer_id"`// experimental / blobber node id + PayerID string `gorm:"column:payer_id"` // optional / client paying for all r/w ops Expiration common.Timestamp `gorm:"column:expiration_date"` AllocationRoot string `gorm:"column:allocation_root"` BlobberSize int64 `gorm:"column:blobber_size"` @@ -32,14 +34,12 @@ type Allocation struct { LatestRedeemedWM string `gorm:"column:latest_redeemed_write_marker"` IsRedeemRequired bool `gorm:"column:is_redeem_required"` TimeUnit time.Duration `gorm:"column:time_unit"` - // ending and cleaning - CleanedUp bool `gorm:"column:cleaned_up"` - Finalized bool `gorm:"column:finalized"` - // Has many terms. - Terms []*Terms `gorm:"-"` - - // Used for 3rd party/payer operations - PayerID string `gorm:"column:payer_id"` + IsImmutable bool `gorm:"is_immutable"` + // Ending and cleaning + CleanedUp bool `gorm:"column:cleaned_up"` + Finalized bool `gorm:"column:finalized"` + // Has many terms + Terms []*Terms `gorm:"-"` } func (Allocation) TableName() string { diff --git a/code/go/0chain.net/blobbercore/allocation/newfilechange.go b/code/go/0chain.net/blobbercore/allocation/newfilechange.go index 280e2cd6d..346ea89fb 100644 --- a/code/go/0chain.net/blobbercore/allocation/newfilechange.go +++ b/code/go/0chain.net/blobbercore/allocation/newfilechange.go @@ -32,6 +32,15 @@ type NewFileChange struct { EncryptedKey string `json:"encrypted_key,omitempty"` CustomMeta string `json:"custom_meta,omitempty"` Attributes reference.Attributes `json:"attributes,omitempty"` + + // IsResumable the request is resumable upload + IsResumable bool `json:"is_resumable,omitempty"` + // UploadLength indicates the size of the entire upload in bytes. The value MUST be a non-negative integer. + UploadLength int64 `json:"upload_length,omitempty"` + // Upload-Offset indicates a byte offset within a resource. The value MUST be a non-negative integer. + UploadOffset int64 `json:"upload_offset,omitempty"` + // IsFinal the request is final chunk + IsFinal bool `json:"is_final,omitempty"` } func (nf *NewFileChange) ProcessChange(ctx context.Context, diff --git a/code/go/0chain.net/blobbercore/allocation/protocol.go b/code/go/0chain.net/blobbercore/allocation/protocol.go index 01c07bf26..9239ef701 100644 --- a/code/go/0chain.net/blobbercore/allocation/protocol.go +++ b/code/go/0chain.net/blobbercore/allocation/protocol.go @@ -122,11 +122,12 @@ func VerifyAllocationTransaction(ctx context.Context, allocationTx string, a.Expiration = sa.Expiration a.OwnerID = sa.OwnerID a.OwnerPublicKey = sa.OwnerPublicKey + a.RepairerID = t.ClientID // blobber node id a.TotalSize = sa.Size a.UsedSize = sa.UsedSize a.Finalized = sa.Finalized - a.PayerID = t.ClientID a.TimeUnit = sa.TimeUnit + a.IsImmutable = sa.IsImmutable // related terms a.Terms = make([]*Terms, 0, len(sa.BlobberDetails)) diff --git a/code/go/0chain.net/blobbercore/blobbergrpc/blobber.pb.go b/code/go/0chain.net/blobbercore/blobbergrpc/blobber.pb.go index 7f2063828..2fb934d7c 100644 --- a/code/go/0chain.net/blobbercore/blobbergrpc/blobber.pb.go +++ b/code/go/0chain.net/blobbercore/blobbergrpc/blobber.pb.go @@ -1435,17 +1435,18 @@ type Allocation struct { UsedSize int64 `protobuf:"varint,4,opt,name=UsedSize,proto3" json:"UsedSize,omitempty"` OwnerID string `protobuf:"bytes,5,opt,name=OwnerID,proto3" json:"OwnerID,omitempty"` OwnerPublicKey string `protobuf:"bytes,6,opt,name=OwnerPublicKey,proto3" json:"OwnerPublicKey,omitempty"` - Expiration int64 `protobuf:"varint,7,opt,name=Expiration,proto3" json:"Expiration,omitempty"` - AllocationRoot string `protobuf:"bytes,8,opt,name=AllocationRoot,proto3" json:"AllocationRoot,omitempty"` - BlobberSize int64 `protobuf:"varint,9,opt,name=BlobberSize,proto3" json:"BlobberSize,omitempty"` - BlobberSizeUsed int64 `protobuf:"varint,10,opt,name=BlobberSizeUsed,proto3" json:"BlobberSizeUsed,omitempty"` - LatestRedeemedWM string `protobuf:"bytes,11,opt,name=LatestRedeemedWM,proto3" json:"LatestRedeemedWM,omitempty"` - IsRedeemRequired bool `protobuf:"varint,12,opt,name=IsRedeemRequired,proto3" json:"IsRedeemRequired,omitempty"` - TimeUnit int64 `protobuf:"varint,13,opt,name=TimeUnit,proto3" json:"TimeUnit,omitempty"` - CleanedUp bool `protobuf:"varint,14,opt,name=CleanedUp,proto3" json:"CleanedUp,omitempty"` - Finalized bool `protobuf:"varint,15,opt,name=Finalized,proto3" json:"Finalized,omitempty"` - Terms []*Term `protobuf:"bytes,16,rep,name=Terms,proto3" json:"Terms,omitempty"` - PayerID string `protobuf:"bytes,17,opt,name=PayerID,proto3" json:"PayerID,omitempty"` + RepairerID string `protobuf:"bytes,7,opt,name=RepairerID,proto3" json:"RepairerID,omitempty"` + PayerID string `protobuf:"bytes,8,opt,name=PayerID,proto3" json:"PayerID,omitempty"` + Expiration int64 `protobuf:"varint,9,opt,name=Expiration,proto3" json:"Expiration,omitempty"` + AllocationRoot string `protobuf:"bytes,10,opt,name=AllocationRoot,proto3" json:"AllocationRoot,omitempty"` + BlobberSize int64 `protobuf:"varint,11,opt,name=BlobberSize,proto3" json:"BlobberSize,omitempty"` + BlobberSizeUsed int64 `protobuf:"varint,12,opt,name=BlobberSizeUsed,proto3" json:"BlobberSizeUsed,omitempty"` + LatestRedeemedWM string `protobuf:"bytes,13,opt,name=LatestRedeemedWM,proto3" json:"LatestRedeemedWM,omitempty"` + IsRedeemRequired bool `protobuf:"varint,14,opt,name=IsRedeemRequired,proto3" json:"IsRedeemRequired,omitempty"` + TimeUnit int64 `protobuf:"varint,15,opt,name=TimeUnit,proto3" json:"TimeUnit,omitempty"` + CleanedUp bool `protobuf:"varint,16,opt,name=CleanedUp,proto3" json:"CleanedUp,omitempty"` + Finalized bool `protobuf:"varint,17,opt,name=Finalized,proto3" json:"Finalized,omitempty"` + Terms []*Term `protobuf:"bytes,18,rep,name=Terms,proto3" json:"Terms,omitempty"` } func (x *Allocation) Reset() { @@ -1522,6 +1523,20 @@ func (x *Allocation) GetOwnerPublicKey() string { return "" } +func (x *Allocation) GetRepairerID() string { + if x != nil { + return x.RepairerID + } + return "" +} + +func (x *Allocation) GetPayerID() string { + if x != nil { + return x.PayerID + } + return "" +} + func (x *Allocation) GetExpiration() int64 { if x != nil { return x.Expiration @@ -1592,13 +1607,6 @@ func (x *Allocation) GetTerms() []*Term { return nil } -func (x *Allocation) GetPayerID() string { - if x != nil { - return x.PayerID - } - return "" -} - type Term struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2319,7 +2327,7 @@ var file_blobber_proto_rawDesc = []byte{ 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0xb6, 0x04, 0x0a, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x6e, 0x22, 0xd6, 0x04, 0x0a, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, 0x54, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x54, 0x78, 0x12, 0x1c, 0x0a, 0x09, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, @@ -2330,31 +2338,33 @@ var file_blobber_proto_rawDesc = []byte{ 0x65, 0x72, 0x49, 0x44, 0x12, 0x26, 0x0a, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, - 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, - 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x53, - 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x62, - 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, - 0x72, 0x53, 0x69, 0x7a, 0x65, 0x55, 0x73, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x55, 0x73, 0x65, 0x64, - 0x12, 0x2a, 0x0a, 0x10, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, - 0x65, 0x64, 0x57, 0x4d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x4c, 0x61, 0x74, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x65, 0x64, 0x57, 0x4d, 0x12, 0x2a, 0x0a, 0x10, - 0x49, 0x73, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, - 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x49, 0x73, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, - 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, - 0x55, 0x6e, 0x69, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, - 0x55, 0x6e, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, 0x55, - 0x70, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, - 0x55, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, - 0x12, 0x2e, 0x0a, 0x05, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x52, 0x05, 0x54, 0x65, 0x72, 0x6d, 0x73, - 0x12, 0x18, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x65, 0x72, 0x49, 0x44, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x50, 0x61, 0x79, 0x65, 0x72, 0x49, 0x44, 0x22, 0x96, 0x01, 0x0a, 0x04, 0x54, + 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x65, 0x72, 0x49, 0x44, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x65, 0x72, 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, + 0x50, 0x61, 0x79, 0x65, 0x72, 0x49, 0x44, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x50, + 0x61, 0x79, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x45, 0x78, 0x70, 0x69, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x20, + 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x28, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x55, + 0x73, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x42, 0x6c, 0x6f, 0x62, 0x62, + 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x55, 0x73, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x4c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x65, 0x64, 0x57, 0x4d, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x65, 0x64, 0x57, 0x4d, 0x12, 0x2a, 0x0a, 0x10, 0x49, 0x73, 0x52, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x10, 0x49, 0x73, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x1c, + 0x0a, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, 0x55, 0x70, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x65, 0x64, 0x55, 0x70, 0x12, 0x1c, 0x0a, 0x09, + 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x05, 0x54, 0x65, + 0x72, 0x6d, 0x73, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x62, + 0x62, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x54, + 0x65, 0x72, 0x6d, 0x52, 0x05, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x22, 0x96, 0x01, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x62, 0x65, 0x72, 0x49, diff --git a/code/go/0chain.net/blobbercore/blobbergrpc/proto/blobber.proto b/code/go/0chain.net/blobbercore/blobbergrpc/proto/blobber.proto index 449b3d8c3..0fb9e7e3a 100644 --- a/code/go/0chain.net/blobbercore/blobbergrpc/proto/blobber.proto +++ b/code/go/0chain.net/blobbercore/blobbergrpc/proto/blobber.proto @@ -184,17 +184,18 @@ message Allocation { int64 UsedSize = 4; string OwnerID = 5; string OwnerPublicKey = 6; - int64 Expiration = 7; - string AllocationRoot = 8; - int64 BlobberSize = 9; - int64 BlobberSizeUsed = 10; - string LatestRedeemedWM = 11; - bool IsRedeemRequired = 12; - int64 TimeUnit = 13; - bool CleanedUp = 14; - bool Finalized = 15; - repeated Term Terms = 16; - string PayerID = 17; + string RepairerID = 7; + string PayerID = 8; + int64 Expiration = 9; + string AllocationRoot = 10; + int64 BlobberSize = 11; + int64 BlobberSizeUsed = 12; + string LatestRedeemedWM = 13; + bool IsRedeemRequired = 14; + int64 TimeUnit = 15; + bool CleanedUp = 16; + bool Finalized = 17; + repeated Term Terms = 18; } message Term { diff --git a/code/go/0chain.net/blobbercore/challenge/entity.go b/code/go/0chain.net/blobbercore/challenge/entity.go index 283c90f46..c47fc7fc3 100644 --- a/code/go/0chain.net/blobbercore/challenge/entity.go +++ b/code/go/0chain.net/blobbercore/challenge/entity.go @@ -75,6 +75,7 @@ type ChallengeEntity struct { ValidationTickets []*ValidationTicket `json:"validation_tickets" gorm:"-"` ObjectPathString datatypes.JSON `json:"-" gorm:"column:object_path"` ObjectPath *reference.ObjectPath `json:"object_path" gorm:"-"` + Created common.Timestamp `json:"created" gorm:"-"` } func (ChallengeEntity) TableName() string { diff --git a/code/go/0chain.net/blobbercore/challenge/worker.go b/code/go/0chain.net/blobbercore/challenge/worker.go index 7fca5bc7e..1c394d97c 100644 --- a/code/go/0chain.net/blobbercore/challenge/worker.go +++ b/code/go/0chain.net/blobbercore/challenge/worker.go @@ -48,7 +48,6 @@ func SubmitProcessedChallenges(ctx context.Context) error { case <-ctx.Done(): return ctx.Err() default: - Logger.Info("Attempting to commit processed challenges...") rctx := datastore.GetStore().CreateTransaction(ctx) db := datastore.GetStore().GetTransaction(rctx) //lastChallengeRedeemed := &ChallengeEntity{} @@ -199,18 +198,22 @@ func FindChallenges(ctx context.Context) { params := make(map[string]string) params["blobber"] = node.Self.ID + var blobberChallenges BCChallengeResponse blobberChallenges.Challenges = make([]*ChallengeEntity, 0) retBytes, err := transaction.MakeSCRestAPICall(transaction.STORAGE_CONTRACT_ADDRESS, "/openchallenges", params, chain.GetServerChain(), nil) + if err != nil { Logger.Error("Error getting the open challenges from the blockchain", zap.Error(err)) } else { tCtx := datastore.GetStore().CreateTransaction(ctx) db := datastore.GetStore().GetTransaction(tCtx) bytesReader := bytes.NewBuffer(retBytes) + d := json.NewDecoder(bytesReader) d.UseNumber() errd := d.Decode(&blobberChallenges) + if errd != nil { Logger.Error("Error in unmarshal of the sharder response", zap.Error(errd)) } else { @@ -219,8 +222,10 @@ func FindChallenges(ctx context.Context) { Logger.Info("No challenge entity from the challenge map") continue } + challengeObj := v _, err := GetChallengeEntity(tCtx, challengeObj.ChallengeID) + if errors.Is(err, gorm.ErrRecordNotFound) { latestChallenge, err := GetLastChallengeEntity(tCtx) if err == nil || errors.Is(err, gorm.ErrRecordNotFound) { diff --git a/code/go/0chain.net/blobbercore/config/config.go b/code/go/0chain.net/blobbercore/config/config.go index bf95a8d8f..2830bda9d 100644 --- a/code/go/0chain.net/blobbercore/config/config.go +++ b/code/go/0chain.net/blobbercore/config/config.go @@ -63,6 +63,11 @@ const ( DeploymentMainNet = 2 ) +type GeolocationConfig struct { + Latitude float64 `mapstructure:"latitude"` + Longitude float64 `mapstructure:"longitude"` +} + type Config struct { *config.Config DBHost string @@ -118,6 +123,8 @@ type Config struct { NumDelegates int `json:"num_delegates"` // ServiceCharge for blobber. ServiceCharge float64 `json:"service_charge"` + + Geolocation GeolocationConfig `mapstructure:"geolocation"` } /*Configuration of the system */ @@ -133,6 +140,17 @@ func Development() bool { return Configuration.DeploymentMode == DeploymentDevelopment } +// get validated geolocatiion +func Geolocation() GeolocationConfig { + g := Configuration.Geolocation + if g.Latitude > 90.00 || g.Latitude < -90.00 || + g.Longitude > 180.00 || g.Longitude < -180.00 { + panic("Fatal error in config file") + + } + return g +} + /*ErrSupportedChain error for indicating which chain is supported by the server */ var ErrSupportedChain error diff --git a/code/go/0chain.net/blobbercore/filestore/chunk_writer.go b/code/go/0chain.net/blobbercore/filestore/chunk_writer.go new file mode 100644 index 000000000..e1e027f03 --- /dev/null +++ b/code/go/0chain.net/blobbercore/filestore/chunk_writer.go @@ -0,0 +1,115 @@ +package filestore + +import ( + "context" + "errors" + "io" + "os" +) + +//ChunkWriter implements a chunk write that will append content to the file +type ChunkWriter struct { + file string + writer *os.File + reader *os.File + offset int64 + size int64 +} + +//NewChunkWriter create a ChunkWriter +func NewChunkWriter(file string) (*ChunkWriter, error) { + w := &ChunkWriter{ + file: file, + } + var f *os.File + fi, err := os.Stat(file) + if errors.Is(err, os.ErrNotExist) { + f, err = os.Create(file) + if err != nil { + return nil, err + } + } else { + f, err = os.OpenFile(file, os.O_RDONLY|os.O_CREATE|os.O_WRONLY, os.ModeAppend) + if err != nil { + return nil, err + } + + w.size = fi.Size() + w.offset = fi.Size() + } + + w.writer = f + + return w, nil +} + +//Write implements io.Writer +func (w *ChunkWriter) Write(b []byte) (n int, err error) { + if w == nil || w.writer == nil { + return 0, os.ErrNotExist + } + + written, err := w.writer.Write(b) + + w.size += int64(written) + + return written, err +} + +//Reader implements io.Reader +func (w *ChunkWriter) Read(p []byte) (n int, err error) { + if w == nil || w.reader == nil { + reader, err := os.Open(w.file) + + if err != nil { + return 0, err + } + + w.reader = reader + } + + return w.reader.Read(p) +} + +//WriteChunk append data to the file +func (w *ChunkWriter) WriteChunk(ctx context.Context, offset int64, src io.Reader) (int64, error) { + if w == nil || w.writer == nil { + return 0, os.ErrNotExist + } + + _, err := w.writer.Seek(offset, io.SeekStart) + + if err != nil { + return 0, err + } + + n, err := io.Copy(w.writer, src) + + w.offset += n + w.size += n + + return n, err +} + +//Size length in bytes for regular files +func (w *ChunkWriter) Size() int64 { + if w == nil { + return 0 + } + return w.size +} + +//Close closes the underline File +func (w *ChunkWriter) Close() { + if w == nil { + return + } + + if w.writer != nil { + w.writer.Close() + } + + if w.reader != nil { + w.reader.Close() + } +} diff --git a/code/go/0chain.net/blobbercore/filestore/chunk_writer_test.go b/code/go/0chain.net/blobbercore/filestore/chunk_writer_test.go new file mode 100644 index 000000000..716dc3189 --- /dev/null +++ b/code/go/0chain.net/blobbercore/filestore/chunk_writer_test.go @@ -0,0 +1,93 @@ +package filestore + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWrite(t *testing.T) { + + fileName := filepath.Join(os.TempDir(), "testwrite_"+strconv.FormatInt(time.Now().Unix(), 10)) + + content := "this is full content" + + w, err := NewChunkWriter(fileName) + if err != nil { + require.Error(t, err, "failed to create ChunkWriter") + return + } + + _, err = w.Write([]byte(content)) + + if err != nil { + require.Error(t, err, "failed to ChunkWriter.WriteChunk") + return + } + + buf := make([]byte, w.Size()) + + //read all lines from file + _, err = w.Read(buf) + if err != nil { + require.Error(t, err, "failed to ChunkWriter.Read") + return + } + + assert.Equal(t, content, string(buf), "File content should be same") +} + +func TestWriteChunk(t *testing.T) { + + chunk1 := "this is 1st chunked" + + tempFile, err := ioutil.TempFile("", "") + + if err != nil { + require.Error(t, err, "failed to create tempfile") + return + } + offset, err := tempFile.Write([]byte(chunk1)) + if err != nil { + require.Error(t, err, "failed to write first chunk to tempfile") + return + } + + fileName := tempFile.Name() + tempFile.Close() + + w, err := NewChunkWriter(fileName) + if err != nil { + require.Error(t, err, "failed to create ChunkWriter") + return + } + defer w.Close() + + chunk2 := "this is 2nd chunk" + + _, err = w.WriteChunk(context.TODO(), int64(offset), strings.NewReader(chunk2)) + + if err != nil { + require.Error(t, err, "failed to ChunkWriter.WriteChunk") + return + } + + buf := make([]byte, w.Size()) + + //read all lines from file + _, err = w.Read(buf) + if err != nil { + require.Error(t, err, "failed to ChunkWriter.Read") + return + } + + assert.Equal(t, chunk1+chunk2, string(buf), "File content should be same") +} diff --git a/code/go/0chain.net/blobbercore/filestore/fs_store.go b/code/go/0chain.net/blobbercore/filestore/fs_store.go index 39c99426c..1ab9be0de 100644 --- a/code/go/0chain.net/blobbercore/filestore/fs_store.go +++ b/code/go/0chain.net/blobbercore/filestore/fs_store.go @@ -2,12 +2,14 @@ package filestore import ( "bytes" + "context" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "hash" "io" + "io/ioutil" "mime/multipart" "os" "path/filepath" @@ -503,21 +505,43 @@ func (fs *FileFSStore) WriteFile(allocationID string, fileData *FileInputData, return nil, common.NewError("filestore_setup_error", "Error setting the fs store. "+err.Error()) } - h := sha1.New() tempFilePath := fs.generateTempPath(allocation, fileData, connectionID) - dest, err := os.Create(tempFilePath) + dest, err := NewChunkWriter(tempFilePath) if err != nil { return nil, common.NewError("file_creation_error", err.Error()) } defer dest.Close() - // infile, err := hdr.Open() - // if err != nil { - // return nil, common.NewError("file_reading_error", err.Error()) - // } + + fileRef := &FileOutputData{} + var fileReader io.Reader = infile + + if fileData.IsResumable { + h := sha1.New() + offset, err := dest.WriteChunk(context.TODO(), fileData.UploadOffset, io.TeeReader(fileReader, h)) + + if err != nil { + return nil, common.NewError("file_write_error", err.Error()) + } + + fileRef.ContentHash = hex.EncodeToString(h.Sum(nil)) + fileRef.Size = dest.Size() + fileRef.Name = fileData.Name + fileRef.Path = fileData.Path + fileRef.UploadOffset = fileData.UploadOffset + offset + fileRef.UploadLength = fileData.UploadLength + + if !fileData.IsFinal { + //skip to compute hash until the last chunk is uploaded + return fileRef, nil + } + + fileReader = dest + } + + h := sha1.New() bytesBuffer := bytes.NewBuffer(nil) - //merkleHash := sha3.New256() multiHashWriter := io.MultiWriter(h, bytesBuffer) - tReader := io.TeeReader(infile, multiHashWriter) + tReader := io.TeeReader(fileReader, multiHashWriter) merkleHashes := make([]hash.Hash, 1024) merkleLeaves := make([]util.Hashable, 1024) for idx := range merkleHashes { @@ -525,7 +549,15 @@ func (fs *FileFSStore) WriteFile(allocationID string, fileData *FileInputData, } fileSize := int64(0) for { - written, err := io.CopyN(dest, tReader, CHUNK_SIZE) + var written int64 + + if fileData.IsResumable { + //all chunks have been written, only read bytes from local file , and compute hash + written, err = io.CopyN(ioutil.Discard, tReader, CHUNK_SIZE) + } else { + written, err = io.CopyN(dest, tReader, CHUNK_SIZE) + } + if err != io.EOF && err != nil { return nil, common.NewError("file_write_error", err.Error()) } @@ -541,7 +573,6 @@ func (fs *FileFSStore) WriteFile(allocationID string, fileData *FileInputData, merkleHashes[offset].Write(dataBytes[i:end]) } - // merkleLeaves = append(merkleLeaves, util.NewStringHashable(hex.EncodeToString(merkleHash.Sum(nil)))) bytesBuffer.Reset() if err != nil && err == io.EOF { break @@ -550,17 +581,21 @@ func (fs *FileFSStore) WriteFile(allocationID string, fileData *FileInputData, for idx := range merkleHashes { merkleLeaves[idx] = util.NewStringHashable(hex.EncodeToString(merkleHashes[idx].Sum(nil))) } - //Logger.Info("File size", zap.Int64("file_size", fileSize)) + var mt util.MerkleTreeI = &util.MerkleTree{} mt.ComputeTree(merkleLeaves) - //Logger.Info("Calculated Merkle root", zap.String("merkle_root", mt.GetRoot()), zap.Int("merkle_leaf_count", len(merkleLeaves))) - fileRef := &FileOutputData{} - fileRef.ContentHash = hex.EncodeToString(h.Sum(nil)) + //only update hash for whole file when it is not a resumable upload or is final chunk. + if !fileData.IsResumable || fileData.IsFinal { + fileRef.ContentHash = hex.EncodeToString(h.Sum(nil)) + } + fileRef.Size = fileSize fileRef.Name = fileData.Name fileRef.Path = fileData.Path fileRef.MerkleRoot = mt.GetRoot() + fileRef.UploadOffset = fileSize + fileRef.UploadLength = fileData.UploadLength return fileRef, nil } diff --git a/code/go/0chain.net/blobbercore/filestore/store.go b/code/go/0chain.net/blobbercore/filestore/store.go index 3e2b718fa..6980b4346 100644 --- a/code/go/0chain.net/blobbercore/filestore/store.go +++ b/code/go/0chain.net/blobbercore/filestore/store.go @@ -14,6 +14,15 @@ type FileInputData struct { Path string Hash string OnCloud bool + + //IsResumable the request is resumable upload + IsResumable bool + //UploadLength indicates the size of the entire upload in bytes. The value MUST be a non-negative integer. + UploadLength int64 + //Upload-Offset indicates a byte offset within a resource. The value MUST be a non-negative integer. + UploadOffset int64 + //IsFinal the request is final chunk + IsFinal bool } type FileOutputData struct { @@ -22,6 +31,11 @@ type FileOutputData struct { MerkleRoot string ContentHash string Size int64 + + //UploadLength indicates the size of the entire upload in bytes. The value MUST be a non-negative integer. + UploadLength int64 + //Upload-Offset indicates a byte offset within a resource. The value MUST be a non-negative integer. + UploadOffset int64 } type FileObjectHandler func(contentHash string, contentSize int64) diff --git a/code/go/0chain.net/blobbercore/handler/convert.go b/code/go/0chain.net/blobbercore/handler/convert.go index bc5f699f5..a61b61b31 100644 --- a/code/go/0chain.net/blobbercore/handler/convert.go +++ b/code/go/0chain.net/blobbercore/handler/convert.go @@ -25,6 +25,8 @@ func AllocationToGRPCAllocation(alloc *allocation.Allocation) *blobbergrpc.Alloc UsedSize: alloc.UsedSize, OwnerID: alloc.OwnerID, OwnerPublicKey: alloc.OwnerPublicKey, + RepairerID: alloc.RepairerID, + PayerID: alloc.PayerID, Expiration: int64(alloc.Expiration), AllocationRoot: alloc.AllocationRoot, BlobberSize: alloc.BlobberSize, @@ -35,7 +37,6 @@ func AllocationToGRPCAllocation(alloc *allocation.Allocation) *blobbergrpc.Alloc CleanedUp: alloc.CleanedUp, Finalized: alloc.Finalized, Terms: terms, - PayerID: alloc.PayerID, } } diff --git a/code/go/0chain.net/blobbercore/handler/dto.go b/code/go/0chain.net/blobbercore/handler/dto.go index 2edcf2b26..98bc27aee 100644 --- a/code/go/0chain.net/blobbercore/handler/dto.go +++ b/code/go/0chain.net/blobbercore/handler/dto.go @@ -12,6 +12,11 @@ type UploadResult struct { Size int64 `json:"size"` Hash string `json:"content_hash"` MerkleRoot string `json:"merkle_root"` + + //UploadLength indicates the size of the entire upload in bytes. The value MUST be a non-negative integer. + UploadLength int64 `json:"upload_length"` + //Upload-Offset indicates a byte offset within a resource. The value MUST be a non-negative integer. + UploadOffset int64 `json:"upload_offset"` } type CommitResult struct { diff --git a/code/go/0chain.net/blobbercore/handler/grpc_handler.go b/code/go/0chain.net/blobbercore/handler/grpc_handler.go index dfb361511..8a1f23ff4 100644 --- a/code/go/0chain.net/blobbercore/handler/grpc_handler.go +++ b/code/go/0chain.net/blobbercore/handler/grpc_handler.go @@ -44,11 +44,10 @@ func (b *blobberGRPCService) GetAllocation(ctx context.Context, request *blobber func (b *blobberGRPCService) GetFileMetaData(ctx context.Context, req *blobbergrpc.GetFileMetaDataRequest) (*blobbergrpc.GetFileMetaDataResponse, error) { logger := ctxzap.Extract(ctx) - allocationObj, err := b.storageHandler.verifyAllocation(ctx, req.Allocation, true) + alloc, err := b.storageHandler.verifyAllocation(ctx, req.Allocation, true) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID clientID := req.Context.Client if len(clientID) == 0 { @@ -61,10 +60,10 @@ func (b *blobberGRPCService) GetFileMetaData(ctx context.Context, req *blobbergr if len(path) == 0 { return nil, common.NewError("invalid_parameters", "Invalid path") } - path_hash = reference.GetReferenceLookup(allocationID, path) + path_hash = reference.GetReferenceLookup(alloc.ID, path) } - fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, alloc.ID, path_hash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } @@ -84,18 +83,22 @@ func (b *blobberGRPCService) GetFileMetaData(ctx context.Context, req *blobbergr logger.Error("Failed to get collaborators from refID", zap.Error(err), zap.Any("ref_id", fileref.ID)) } - authTokenString := req.AuthToken - - if (allocationObj.OwnerID != clientID && - allocationObj.PayerID != clientID && - !b.packageHandler.IsACollaborator(ctx, fileref.ID, clientID)) || len(authTokenString) > 0 { - authTicketVerified, err := b.storageHandler.verifyAuthTicket(ctx, req.AuthToken, allocationObj, fileref, clientID) - if err != nil { - return nil, err - } - if !authTicketVerified { - return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.") + // authorize file access + var ( + isOwner = clientID == alloc.OwnerID + isRepairer = clientID == alloc.RepairerID + isCollaborator = b.packageHandler.IsACollaborator(ctx, fileref.ID, clientID) + ) + + if !isOwner && !isRepairer && !isCollaborator { + // check auth token + if isAuthorized, err := b.storageHandler.verifyAuthTicket(ctx, + req.AuthToken, alloc, fileref, clientID, + ); !isAuthorized { + return nil, common.NewErrorf("download_file", + "cannot verify auth ticket: %v", err) } + fileref.Path = "" } @@ -116,15 +119,14 @@ func (b *blobberGRPCService) GetFileMetaData(ctx context.Context, req *blobbergr func (b *blobberGRPCService) GetFileStats(ctx context.Context, req *blobbergrpc.GetFileStatsRequest) (*blobbergrpc.GetFileStatsResponse, error) { allocationTx := req.Context.Allocation - allocationObj, err := b.storageHandler.verifyAllocation(ctx, allocationTx, true) + alloc, err := b.storageHandler.verifyAllocation(ctx, allocationTx, true) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID clientID := req.Context.Client - if len(clientID) == 0 || allocationObj.OwnerID != clientID { + if len(clientID) == 0 || alloc.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } @@ -134,10 +136,10 @@ func (b *blobberGRPCService) GetFileStats(ctx context.Context, req *blobbergrpc. if len(path) == 0 { return nil, common.NewError("invalid_parameters", "Invalid path") } - path_hash = reference.GetReferenceLookup(allocationID, path) + path_hash = reference.GetReferenceLookup(alloc.ID, path) } - fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, alloc.ID, path_hash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) @@ -164,12 +166,11 @@ func (b *blobberGRPCService) ListEntities(ctx context.Context, req *blobbergrpc. clientID := req.Context.Client allocationTx := req.Context.Allocation - allocationObj, err := b.storageHandler.verifyAllocation(ctx, allocationTx, true) + alloc, err := b.storageHandler.verifyAllocation(ctx, allocationTx, true) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID if len(clientID) == 0 { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") @@ -181,18 +182,18 @@ func (b *blobberGRPCService) ListEntities(ctx context.Context, req *blobbergrpc. if len(path) == 0 { return nil, common.NewError("invalid_parameters", "Invalid path") } - path_hash = reference.GetReferenceLookup(allocationID, path) + path_hash = reference.GetReferenceLookup(alloc.ID, path) } logger.Info("Path Hash for list dir :" + path_hash) - fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, allocationID, path_hash) + fileref, err := b.packageHandler.GetReferenceFromLookupHash(ctx, alloc.ID, path_hash) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid path. "+err.Error()) } authTokenString := req.AuthToken - if clientID != allocationObj.OwnerID || len(authTokenString) > 0 { - authTicketVerified, err := b.storageHandler.verifyAuthTicket(ctx, authTokenString, allocationObj, fileref, clientID) + if clientID != alloc.OwnerID || len(authTokenString) > 0 { + authTicketVerified, err := b.storageHandler.verifyAuthTicket(ctx, authTokenString, alloc, fileref, clientID) if err != nil { return nil, err } @@ -201,18 +202,18 @@ func (b *blobberGRPCService) ListEntities(ctx context.Context, req *blobbergrpc. } } - dirref, err := b.packageHandler.GetRefWithChildren(ctx, allocationID, fileref.Path) + dirref, err := b.packageHandler.GetRefWithChildren(ctx, alloc.ID, fileref.Path) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid path. "+err.Error()) } - if clientID != allocationObj.OwnerID { + if clientID != alloc.OwnerID { dirref.Path = "" } var entities []*blobbergrpc.FileRef for _, entity := range dirref.Children { - if clientID != allocationObj.OwnerID { + if clientID != alloc.OwnerID { entity.Path = "" } entities = append(entities, reference.FileRefToFileRefGRPC(entity)) @@ -221,22 +222,21 @@ func (b *blobberGRPCService) ListEntities(ctx context.Context, req *blobbergrpc. refGRPC.DirMetaData.Children = entities return &blobbergrpc.ListEntitiesResponse{ - AllocationRoot: allocationObj.AllocationRoot, + AllocationRoot: alloc.AllocationRoot, MetaData: refGRPC, }, nil } func (b *blobberGRPCService) GetObjectPath(ctx context.Context, req *blobbergrpc.GetObjectPathRequest) (*blobbergrpc.GetObjectPathResponse, error) { allocationTx := req.Context.Allocation - allocationObj, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) + alloc, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID clientID := req.Context.Client - if len(clientID) == 0 || allocationObj.OwnerID != clientID { + if len(clientID) == 0 || alloc.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } path := req.Path @@ -254,16 +254,16 @@ func (b *blobberGRPCService) GetObjectPath(ctx context.Context, req *blobbergrpc return nil, common.NewError("invalid_parameters", "Invalid block number") } - objectPath, err := b.packageHandler.GetObjectPathGRPC(ctx, allocationID, blockNum) + objectPath, err := b.packageHandler.GetObjectPathGRPC(ctx, alloc.ID, blockNum) if err != nil { return nil, err } var latestWM *writemarker.WriteMarkerEntity - if len(allocationObj.AllocationRoot) == 0 { + if len(alloc.AllocationRoot) == 0 { latestWM = nil } else { - latestWM, err = b.packageHandler.GetWriteMarkerEntity(ctx, allocationObj.AllocationRoot) + latestWM, err = b.packageHandler.GetWriteMarkerEntity(ctx, alloc.AllocationRoot) if err != nil { return nil, common.NewError("latest_write_marker_read_error", "Error reading the latest write marker for allocation."+err.Error()) } @@ -281,12 +281,11 @@ func (b *blobberGRPCService) GetObjectPath(ctx context.Context, req *blobbergrpc func (b *blobberGRPCService) GetReferencePath(ctx context.Context, req *blobbergrpc.GetReferencePathRequest) (*blobbergrpc.GetReferencePathResponse, error) { allocationTx := req.Context.Allocation - allocationObj, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) + alloc, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID clientID := req.Context.Client if len(clientID) == 0 { @@ -308,7 +307,7 @@ func (b *blobberGRPCService) GetReferencePath(ctx context.Context, req *blobberg } } - rootRef, err := b.packageHandler.GetReferencePathFromPaths(ctx, allocationID, paths) + rootRef, err := b.packageHandler.GetReferencePathFromPaths(ctx, alloc.ID, paths) if err != nil { return nil, err } @@ -330,10 +329,10 @@ func (b *blobberGRPCService) GetReferencePath(ctx context.Context, req *blobberg } var latestWM *writemarker.WriteMarkerEntity - if len(allocationObj.AllocationRoot) == 0 { + if len(alloc.AllocationRoot) == 0 { latestWM = nil } else { - latestWM, err = writemarker.GetWriteMarkerEntity(ctx, allocationObj.AllocationRoot) + latestWM, err = writemarker.GetWriteMarkerEntity(ctx, alloc.AllocationRoot) if err != nil { return nil, common.NewError("latest_write_marker_read_error", "Error reading the latest write marker for allocation."+err.Error()) } @@ -349,15 +348,14 @@ func (b *blobberGRPCService) GetReferencePath(ctx context.Context, req *blobberg func (b *blobberGRPCService) GetObjectTree(ctx context.Context, req *blobbergrpc.GetObjectTreeRequest) (*blobbergrpc.GetObjectTreeResponse, error) { allocationTx := req.Context.Allocation - allocationObj, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) + alloc, err := b.storageHandler.verifyAllocation(ctx, allocationTx, false) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID clientID := req.Context.Client - if len(clientID) == 0 || allocationObj.OwnerID != clientID { + if len(clientID) == 0 || alloc.OwnerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } path := req.Path @@ -365,7 +363,7 @@ func (b *blobberGRPCService) GetObjectTree(ctx context.Context, req *blobbergrpc return nil, common.NewError("invalid_parameters", "Invalid path") } - rootRef, err := b.packageHandler.GetObjectTree(ctx, allocationID, path) + rootRef, err := b.packageHandler.GetObjectTree(ctx, alloc.ID, path) if err != nil { return nil, err } @@ -387,10 +385,10 @@ func (b *blobberGRPCService) GetObjectTree(ctx context.Context, req *blobbergrpc } var latestWM *writemarker.WriteMarkerEntity - if len(allocationObj.AllocationRoot) == 0 { + if len(alloc.AllocationRoot) == 0 { latestWM = nil } else { - latestWM, err = writemarker.GetWriteMarkerEntity(ctx, allocationObj.AllocationRoot) + latestWM, err = writemarker.GetWriteMarkerEntity(ctx, alloc.AllocationRoot) if err != nil { return nil, common.NewError("latest_write_marker_read_error", "Error reading the latest write marker for allocation."+err.Error()) } diff --git a/code/go/0chain.net/blobbercore/handler/helper.go b/code/go/0chain.net/blobbercore/handler/helper.go index b412aa718..4cced3dbb 100644 --- a/code/go/0chain.net/blobbercore/handler/helper.go +++ b/code/go/0chain.net/blobbercore/handler/helper.go @@ -16,12 +16,9 @@ import ( ) func setupGRPCHandlerContext(ctx context.Context, r *blobbergrpc.RequestContext) context.Context { - ctx = context.WithValue(ctx, constants.CLIENT_CONTEXT_KEY, - r.Client) - ctx = context.WithValue(ctx, constants.CLIENT_KEY_CONTEXT_KEY, - r.ClientKey) - ctx = context.WithValue(ctx, constants.ALLOCATION_CONTEXT_KEY, - r.Allocation) + ctx = context.WithValue(ctx, constants.CLIENT_CONTEXT_KEY, r.Client) + ctx = context.WithValue(ctx, constants.CLIENT_KEY_CONTEXT_KEY, r.ClientKey) + ctx = context.WithValue(ctx, constants.ALLOCATION_CONTEXT_KEY, r.Allocation) return ctx } diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index d7fb4eba1..5f43e3946 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -184,38 +184,34 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "invalid method used (GET), use POST instead") } + // get client and allocation ids var ( - allocationTx = ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) clientID = ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) - - allocationObj *allocation.Allocation + allocationTx = ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) + _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) // runtime type check + alloc *allocation.Allocation ) + // check client if len(clientID) == 0 { return nil, common.NewError("download_file", "invalid client") } - // runtime type check - _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) - - // verify or update allocation - allocationObj, err = fsh.verifyAllocation(ctx, allocationTx, false) + // get and check allocation + alloc, err = fsh.verifyAllocation(ctx, allocationTx, false) if err != nil { return nil, common.NewErrorf("download_file", "invalid allocation id passed: %v", err) } - var allocationID = allocationObj.ID - + // get and parse file params if err = r.ParseMultipartForm(FORM_FILE_PARSE_MAX_MEMORY); nil != err { Logger.Info("download_file - request_parse_error", zap.Error(err)) return nil, common.NewErrorf("download_file", "request_parse_error: %v", err) } - rxPay := r.FormValue("rx_pay") == "true" - - pathHash, err := pathHashFromReq(r, allocationID) + pathHash, err := pathHashFromReq(r, alloc.ID) if err != nil { return nil, common.NewError("download_file", "invalid path") } @@ -243,6 +239,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "invalid number of blocks") } + // get read marker var ( readMarkerString = r.FormValue("read_marker") readMarker = &readmarker.ReadMarker{} @@ -256,14 +253,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( var rmObj = &readmarker.ReadMarkerEntity{} rmObj.LatestRM = readMarker - if err = rmObj.VerifyMarker(ctx, allocationObj); err != nil { + if err = rmObj.VerifyMarker(ctx, alloc); err != nil { return nil, common.NewErrorf("download_file", "invalid read marker, "+ "failed to verify the read marker: %v", err) } + // get file reference var fileref *reference.Ref - fileref, err = reference.GetReferenceFromLookupHash(ctx, allocationID, - pathHash) + fileref, err = reference.GetReferenceFromLookupHash(ctx, alloc.ID, pathHash) if err != nil { return nil, common.NewErrorf("download_file", "invalid file path: %v", err) @@ -274,40 +271,37 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "path is not a file: %v", err) } - var ( - authTokenString = r.FormValue("auth_token") - clientIDForReadRedeem = clientID // default payer is client - isACollaborator = reference.IsACollaborator(ctx, fileref.ID, clientID) - ) + // set payer: default + var payerID = alloc.OwnerID - // Owner will pay for collaborator - if isACollaborator { - clientIDForReadRedeem = allocationObj.OwnerID + // set payer: check for explicit allocation payer value + if len(alloc.PayerID) > 0 { + payerID = alloc.PayerID } - var attrs *reference.Attributes - if attrs, err = fileref.GetAttributes(); err != nil { - return nil, common.NewErrorf("download_file", - "error getting file attributes: %v", err) + // set payer: check for command line payer flag (--rx_pay) + if r.FormValue("rx_pay") == "true" { + payerID = clientID } - var authToken *readmarker.AuthTicket = nil + // authorize file access + var ( + isOwner = clientID == alloc.OwnerID + isRepairer = clientID == alloc.RepairerID + isCollaborator = reference.IsACollaborator(ctx, fileref.ID, clientID) + ) - if (allocationObj.OwnerID != clientID && - allocationObj.PayerID != clientID && - !isACollaborator) || len(authTokenString) > 0 { + var authToken *readmarker.AuthTicket = nil - var authTicketVerified bool - authTicketVerified, err = fsh.verifyAuthTicket(ctx, r.FormValue("auth_token"), allocationObj, - fileref, clientID) - if err != nil { - return nil, common.NewErrorf("download_file", - "verifying auth ticket: %v", err) - } + if !isOwner && !isRepairer && !isCollaborator { + var authTokenString = r.FormValue("auth_token") - if !authTicketVerified { + // check auth token + if isAuthorized, err := fsh.verifyAuthTicket(ctx, + authTokenString, alloc, fileref, clientID, + ); !isAuthorized { return nil, common.NewErrorf("download_file", - "could not verify the auth ticket") + "cannot verify auth ticket: %v", err) } authToken = &readmarker.AuthTicket{} @@ -335,22 +329,26 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( return nil, errors.New("auth ticket is not authorized to download file specified") } } + readMarker.AuthTicket = datatypes.JSON(authTokenString) - // if --rx_pay used 3rd_party pays - if rxPay { - clientIDForReadRedeem = clientID - } else if attrs.WhoPaysForReads == common.WhoPaysOwner { - clientIDForReadRedeem = allocationObj.OwnerID // owner pays + // check for file payer flag + if fileAttrs, err := fileref.GetAttributes(); err != nil { + return nil, common.NewErrorf("download_file", + "error getting file attributes: %v", err) + } else { + if fileAttrs.WhoPaysForReads == common.WhoPays3rdParty { + payerID = clientID + } } - - readMarker.AuthTicket = datatypes.JSON(authTokenString) } + // create read marker var ( rme *readmarker.ReadMarkerEntity latestRM *readmarker.ReadMarker pendNumBlocks int64 ) + rme, err = readmarker.GetLatestReadMarkerEntity(ctx, clientID) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, common.NewErrorf("download_file", @@ -378,15 +376,13 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( } // check out read pool tokens if read_price > 0 - err = readPreRedeem(ctx, allocationObj, numBlocks, pendNumBlocks, - clientIDForReadRedeem) + err = readPreRedeem(ctx, alloc, numBlocks, pendNumBlocks, payerID) if err != nil { return nil, common.NewErrorf("download_file", "pre-redeeming read marker: %v", err) } - // reading allowed - + // reading is allowed var ( downloadMode = r.FormValue("content") respData []byte @@ -397,7 +393,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( fileData.Path = fileref.Path fileData.Hash = fileref.ThumbnailHash fileData.OnCloud = fileref.OnCloud - respData, err = filestore.GetFileStore().GetFileBlock(allocationID, + respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, blockNum, numBlocks) if err != nil { return nil, common.NewErrorf("download_file", @@ -409,7 +405,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( fileData.Path = fileref.Path fileData.Hash = fileref.ContentHash fileData.OnCloud = fileref.OnCloud - respData, err = filestore.GetFileStore().GetFileBlock(allocationID, + respData, err = filestore.GetFileStore().GetFileBlock(alloc.ID, fileData, blockNum, numBlocks) if err != nil { return nil, common.NewErrorf("download_file", @@ -417,7 +413,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( } } - readMarker.PayerID = clientIDForReadRedeem + readMarker.PayerID = payerID err = readmarker.SaveLatestReadMarker(ctx, readMarker, latestRM == nil) if err != nil { return nil, common.NewErrorf("download_file", @@ -511,6 +507,10 @@ func (fsh *StorageHandler) CommitWrite(ctx context.Context, r *http.Request) (*C return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } + if allocationObj.IsImmutable { + return nil, common.NewError("immutable_allocation", "Cannot write to an immutable allocation") + } + allocationID := allocationObj.ID connectionID := r.FormValue("connection_id") @@ -532,7 +532,7 @@ func (fsh *StorageHandler) CommitWrite(ctx context.Context, r *http.Request) (*C "Invalid connection id. Connection does not have any changes.") } - var isACollaborator bool + var isCollaborator bool for _, change := range connectionObj.Changes { if change.Operation == allocation.UPDATE_OPERATION { updateFileChange := new(allocation.UpdateFileChange) @@ -543,7 +543,7 @@ func (fsh *StorageHandler) CommitWrite(ctx context.Context, r *http.Request) (*C if err != nil { return nil, err } - isACollaborator = reference.IsACollaborator(ctx, fileRef.ID, clientID) + isCollaborator = reference.IsACollaborator(ctx, fileRef.ID, clientID) break } } @@ -552,7 +552,7 @@ func (fsh *StorageHandler) CommitWrite(ctx context.Context, r *http.Request) (*C return nil, common.NewError("invalid_params", "Please provide clientID and clientKey") } - if (allocationObj.OwnerID != clientID || encryption.Hash(clientKeyBytes) != clientID) && !isACollaborator { + if (allocationObj.OwnerID != clientID || encryption.Hash(clientKeyBytes) != clientID) && !isCollaborator { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } @@ -603,7 +603,7 @@ func (fsh *StorageHandler) CommitWrite(ctx context.Context, r *http.Request) (*C } var clientIDForWriteRedeem = writeMarker.ClientID - if isACollaborator { + if isCollaborator { clientIDForWriteRedeem = allocationObj.OwnerID } @@ -677,6 +677,11 @@ func (fsh *StorageHandler) RenameObject(ctx context.Context, r *http.Request) (i if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } + + if allocationObj.IsImmutable { + return nil, common.NewError("immutable_allocation", "Cannot rename data in an immutable allocation") + } + allocationID := allocationObj.ID clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) @@ -775,6 +780,10 @@ func (fsh *StorageHandler) UpdateObjectAttributes(ctx context.Context, return nil, common.NewError("invalid_signature", "Invalid signature") } + if alloc.IsImmutable { + return nil, common.NewError("immutable_allocation", "Cannot update data in an immutable allocation") + } + // runtime type check _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) @@ -871,6 +880,10 @@ func (fsh *StorageHandler) CopyObject(ctx context.Context, r *http.Request) (int return nil, common.NewError("invalid_signature", "Invalid signature") } + if allocationObj.IsImmutable { + return nil, common.NewError("immutable_allocation", "Cannot copy data in an immutable allocation") + } + clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) _ = ctx.Value(constants.CLIENT_KEY_CONTEXT_KEY).(string) @@ -1003,6 +1016,10 @@ func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*Upl return nil, common.NewError("invalid_signature", "Invalid signature") } + if allocationObj.IsImmutable { + return nil, common.NewError("immutable_allocation", "Cannot write to an immutable allocation") + } + allocationID := allocationObj.ID if len(clientID) == 0 { @@ -1037,7 +1054,7 @@ func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*Upl } if mode == allocation.DELETE_OPERATION { - if allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID { + if allocationObj.OwnerID != clientID && allocationObj.RepairerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") } result, err = fsh.DeleteFile(ctx, r, connectionObj) @@ -1060,7 +1077,7 @@ func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*Upl existingFileRefSize := int64(0) exisitingFileOnCloud := false if mode == allocation.INSERT_OPERATION { - if allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID { + if allocationObj.OwnerID != clientID && allocationObj.RepairerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") } @@ -1073,7 +1090,7 @@ func (fsh *StorageHandler) WriteFile(ctx context.Context, r *http.Request) (*Upl } if allocationObj.OwnerID != clientID && - allocationObj.PayerID != clientID && + allocationObj.RepairerID != clientID && !reference.IsACollaborator(ctx, exisitingFileRef.ID, clientID) { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner, collaborator or the payer of the allocation") } diff --git a/code/go/0chain.net/blobbercore/handler/protocol.go b/code/go/0chain.net/blobbercore/handler/protocol.go index 9a4bfa9e8..a490de673 100644 --- a/code/go/0chain.net/blobbercore/handler/protocol.go +++ b/code/go/0chain.net/blobbercore/handler/protocol.go @@ -1,19 +1,20 @@ package handler import ( - "context" - "encoding/json" - "errors" "sync" "time" + "errors" + "context" + "encoding/json" + "go.uber.org/zap" + "github.com/0chain/gosdk/zcncore" "0chain.net/blobbercore/config" - . "0chain.net/core/logging" + "0chain.net/core/chain" "0chain.net/core/node" "0chain.net/core/transaction" - - "github.com/0chain/gosdk/zcncore" - "go.uber.org/zap" + "0chain.net/core/util" + . "0chain.net/core/logging" ) const ( @@ -56,21 +57,50 @@ func (ar *apiResp) err() error { //nolint:unused,deadcode // might be used later return nil } -func RegisterBlobber(ctx context.Context) (string, error) { - wcb := &WalletCallback{} - wcb.wg = &sync.WaitGroup{} - wcb.wg.Add(1) - err := zcncore.RegisterToMiners(node.Self.GetWallet(), wcb) - if err != nil { - return "", err +func getStorageNode() (*transaction.StorageNode, error) { + var err error + sn := &transaction.StorageNode{} + sn.ID = node.Self.ID + sn.BaseURL = node.Self.GetURLBase() + sn.Geolocation = transaction.StorageNodeGeolocation(config.Geolocation()) + sn.Capacity = config.Configuration.Capacity + readPrice := config.Configuration.ReadPrice + writePrice := config.Configuration.WritePrice + if config.Configuration.PriceInUSD { + readPrice, err = zcncore.ConvertUSDToToken(readPrice) + if err != nil { + return nil, err + } + + writePrice, err = zcncore.ConvertUSDToToken(writePrice) + if err != nil { + return nil, err + } } + sn.Terms.ReadPrice = zcncore.ConvertToValue(readPrice) + sn.Terms.WritePrice = zcncore.ConvertToValue(writePrice) + sn.Terms.MinLockDemand = config.Configuration.MinLockDemand + sn.Terms.MaxOfferDuration = config.Configuration.MaxOfferDuration + sn.Terms.ChallengeCompletionTime = config.Configuration.ChallengeCompletionTime + sn.StakePoolSettings.DelegateWallet = config.Configuration.DelegateWallet + sn.StakePoolSettings.MinStake = config.Configuration.MinStake + sn.StakePoolSettings.MaxStake = config.Configuration.MaxStake + sn.StakePoolSettings.NumDelegates = config.Configuration.NumDelegates + sn.StakePoolSettings.ServiceCharge = config.Configuration.ServiceCharge + return sn, nil +} + +// Add or update blobber on blockchain +func BlobberAdd(ctx context.Context) (string, error) { time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) + // initialize storage node (ie blobber) txn, err := transaction.NewTransactionEntity() if err != nil { return "", err } + sn, err := getStorageNode() if err != nil { return "", err @@ -80,11 +110,13 @@ func RegisterBlobber(ctx context.Context) (string, error) { if err != nil { return "", err } - Logger.Info("Adding blobber to the blockchain.") + + Logger.Info("Adding or updating on the blockchain") + err = txn.ExecuteSmartContract(transaction.STORAGE_CONTRACT_ADDRESS, transaction.ADD_BLOBBER_SC_NAME, string(snBytes), 0) if err != nil { - Logger.Info("Failed during registering blobber to the mining network", + Logger.Info("Failed to set blobber on the blockchain", zap.String("err:", err.Error())) return "", err } @@ -102,15 +134,16 @@ func BlobberHealthCheck(ctx context.Context) (string, error) { if config.Configuration.Capacity == 0 { return "", ErrBlobberHasRemoved } + txn, err := transaction.NewTransactionEntity() if err != nil { return "", err } - Logger.Info("Blobber health check to the blockchain.") + err = txn.ExecuteSmartContract(transaction.STORAGE_CONTRACT_ADDRESS, transaction.BLOBBER_HEALTH_CHECK, "", 0) if err != nil { - Logger.Info("Failed during blobber health check to the mining network", + Logger.Info("Failed to health check on the blockchain", zap.String("err:", err.Error())) return "", err } @@ -118,63 +151,26 @@ func BlobberHealthCheck(ctx context.Context) (string, error) { return txn.Hash, nil } -func UpdateBlobberSettings(ctx context.Context) (string, error) { - txn, err := transaction.NewTransactionEntity() - if err != nil { - return "", err - } - - sn, err := getStorageNode() - if err != nil { - return "", err - } - - snBytes, err := json.Marshal(sn) - if err != nil { - return "", err - } +func TransactionVerify(txnHash string) (t *transaction.Transaction, err error) { + time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) - Logger.Info("Updating settings to the blockchain.") - err = txn.ExecuteSmartContract(transaction.STORAGE_CONTRACT_ADDRESS, - transaction.UPDATE_BLOBBER_SETTINGS, string(snBytes), 0) - if err != nil { - Logger.Info("Failed during updating settings to the mining network", - zap.String("err:", err.Error())) - return "", err + for i := 0; i < util.MAX_RETRIES; i++ { + time.Sleep(transaction.SLEEP_FOR_TXN_CONFIRMATION * time.Second) + if t, err = transaction.VerifyTransaction(txnHash, chain.GetServerChain()); err == nil { + return t, nil + } } - return txn.Hash, nil + return } -func getStorageNode() (*transaction.StorageNode, error) { - var err error - sn := &transaction.StorageNode{} - sn.ID = node.Self.ID - sn.BaseURL = node.Self.GetURLBase() - sn.Capacity = config.Configuration.Capacity - readPrice := config.Configuration.ReadPrice - writePrice := config.Configuration.WritePrice - if config.Configuration.PriceInUSD { - readPrice, err = zcncore.ConvertUSDToToken(readPrice) - if err != nil { - return nil, err - } - - writePrice, err = zcncore.ConvertUSDToToken(writePrice) - if err != nil { - return nil, err - } +func WalletRegister() error { + wcb := &WalletCallback{} + wcb.wg = &sync.WaitGroup{} + wcb.wg.Add(1) + if err := zcncore.RegisterToMiners(node.Self.GetWallet(), wcb); err != nil { + return err } - sn.Terms.ReadPrice = zcncore.ConvertToValue(readPrice) - sn.Terms.WritePrice = zcncore.ConvertToValue(writePrice) - sn.Terms.MinLockDemand = config.Configuration.MinLockDemand - sn.Terms.MaxOfferDuration = config.Configuration.MaxOfferDuration - sn.Terms.ChallengeCompletionTime = config.Configuration.ChallengeCompletionTime - sn.StakePoolSettings.DelegateWallet = config.Configuration.DelegateWallet - sn.StakePoolSettings.MinStake = config.Configuration.MinStake - sn.StakePoolSettings.MaxStake = config.Configuration.MaxStake - sn.StakePoolSettings.NumDelegates = config.Configuration.NumDelegates - sn.StakePoolSettings.ServiceCharge = config.Configuration.ServiceCharge - return sn, nil + return nil } diff --git a/code/go/0chain.net/blobbercore/handler/storage_handler.go b/code/go/0chain.net/blobbercore/handler/storage_handler.go index a8fdc366b..a6dbb6daa 100644 --- a/code/go/0chain.net/blobbercore/handler/storage_handler.go +++ b/code/go/0chain.net/blobbercore/handler/storage_handler.go @@ -123,12 +123,12 @@ func (fsh *StorageHandler) GetFileMeta(ctx context.Context, r *http.Request) (in return nil, common.NewError("invalid_method", "Invalid method used. Use POST instead") } allocationTx := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) - allocationObj, err := fsh.verifyAllocation(ctx, allocationTx, true) + alloc, err := fsh.verifyAllocation(ctx, allocationTx, true) if err != nil { return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - allocationID := allocationObj.ID + allocationID := alloc.ID clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if len(clientID) == 0 { @@ -164,18 +164,24 @@ func (fsh *StorageHandler) GetFileMeta(ctx context.Context, r *http.Request) (in result["collaborators"] = collaborators - authTokenString := r.FormValue("auth_token") + // authorize file access + var ( + isOwner = clientID == alloc.OwnerID + isRepairer = clientID == alloc.RepairerID + isCollaborator = reference.IsACollaborator(ctx, fileref.ID, clientID) + ) - if (allocationObj.OwnerID != clientID && - allocationObj.PayerID != clientID && - !reference.IsACollaborator(ctx, fileref.ID, clientID)) || len(authTokenString) > 0 { - authTicketVerified, err := fsh.verifyAuthTicket(ctx, r.FormValue("auth_token"), allocationObj, fileref, clientID) - if err != nil { - return nil, err - } - if !authTicketVerified { - return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket.") + if !isOwner && !isRepairer && !isCollaborator { + var authTokenString = r.FormValue("auth_token") + + // check auth token + if isAuthorized, err := fsh.verifyAuthTicket(ctx, + authTokenString, alloc, fileref, clientID, + ); !isAuthorized { + return nil, common.NewErrorf("download_file", + "cannot verify auth ticket: %v", err) } + delete(result, "path") } @@ -696,7 +702,7 @@ func (fsh *StorageHandler) CalculateHash(ctx context.Context, r *http.Request) ( // verifySignatureFromRequest verifyes signature passed as common.ClientSignatureHeader header. func verifySignatureFromRequest(r *http.Request, pbK string) (bool, error) { - sign := r.Header.Get(common.ClientSignatureHeader) + sign := encryption.MiraclToHerumiSig(r.Header.Get(common.ClientSignatureHeader)) if len(sign) < 64 { return false, nil } @@ -707,7 +713,9 @@ func verifySignatureFromRequest(r *http.Request, pbK string) (bool, error) { return false, common.NewError("invalid_params", "Missing allocation tx") } - return encryption.Verify(pbK, sign, encryption.Hash(data)) + hash := encryption.Hash(data) + pbK = encryption.MiraclToHerumiPK(pbK) + return encryption.Verify(pbK, sign, hash) } // pathsFromReq retrieves paths value from request which can be represented as single "path" value or "paths" values, diff --git a/code/go/0chain.net/blobbercore/handler/zcncore.go b/code/go/0chain.net/blobbercore/handler/zcncore.go index 2966ee921..175889f82 100644 --- a/code/go/0chain.net/blobbercore/handler/zcncore.go +++ b/code/go/0chain.net/blobbercore/handler/zcncore.go @@ -2,6 +2,7 @@ package handler import ( "sync" + "encoding/json" "github.com/0chain/gosdk/core/common" "github.com/0chain/gosdk/zcncore" @@ -11,6 +12,7 @@ type ZCNStatus struct { wg *sync.WaitGroup success bool balance int64 + info string } func (zcn *ZCNStatus) OnBalanceAvailable(status int, value int64, info string) { @@ -23,6 +25,16 @@ func (zcn *ZCNStatus) OnBalanceAvailable(status int, value int64, info string) { zcn.balance = value } +func (zcn *ZCNStatus) OnInfoAvailable(op int, status int, info string, err string) { + defer zcn.wg.Done() + if status == zcncore.StatusSuccess { + zcn.success = true + } else { + zcn.success = false + } + zcn.info = info +} + func (zcn *ZCNStatus) OnTransactionComplete(t *zcncore.Transaction, status int) { defer zcn.wg.Done() if status == zcncore.StatusSuccess { @@ -49,7 +61,7 @@ func CheckBalance() (float64, error) { wg.Add(1) err := zcncore.GetBalance(statusBar) if err != nil { - return 0, common.NewError("check_balance_failed", "Call to GetBalance failed with err: "+err.Error()) + return 0, common.NewError("check_balance_failed", "Call to GetBalance failed with err: " + err.Error()) } wg.Wait() if !statusBar.success { @@ -58,6 +70,32 @@ func CheckBalance() (float64, error) { return zcncore.ConvertToToken(statusBar.balance), nil } +func GetBlobbers() ([]*zcncore.Blobber, error) { + var info struct { + Nodes []*zcncore.Blobber + } + + wg := &sync.WaitGroup{} + statusBar := &ZCNStatus{wg: wg} + wg.Add(1) + + err := zcncore.GetBlobbers(statusBar) + if err != nil { + return info.Nodes, common.NewError("get_blobbers_failed", "Call to GetBlobbers failed with err: " + err.Error()) + } + wg.Wait() + + if !statusBar.success { + return info.Nodes, nil + } + + if err = json.Unmarshal([]byte(statusBar.info), &info); err != nil { + return info.Nodes, common.NewError("get_blobbers_failed", "Decoding response to GetBlobbers failed with err: " + err.Error()) + } + + return info.Nodes, nil +} + func CallFaucet() error { wg := &sync.WaitGroup{} statusBar := &ZCNStatus{wg: wg} diff --git a/code/go/0chain.net/blobbercore/openapi/blobber.swagger.json b/code/go/0chain.net/blobbercore/openapi/blobber.swagger.json index 327425609..05beb9483 100644 --- a/code/go/0chain.net/blobbercore/openapi/blobber.swagger.json +++ b/code/go/0chain.net/blobbercore/openapi/blobber.swagger.json @@ -485,6 +485,12 @@ "OwnerPublicKey": { "type": "string" }, + "RepairerID": { + "type": "string" + }, + "PayerID": { + "type": "string" + }, "Expiration": { "type": "string", "format": "int64" @@ -521,9 +527,6 @@ "items": { "$ref": "#/definitions/v1Term" } - }, - "PayerID": { - "type": "string" } } }, diff --git a/code/go/0chain.net/blobbercore/readmarker/entity.go b/code/go/0chain.net/blobbercore/readmarker/entity.go index d01030834..a486ade2e 100644 --- a/code/go/0chain.net/blobbercore/readmarker/entity.go +++ b/code/go/0chain.net/blobbercore/readmarker/entity.go @@ -108,7 +108,6 @@ func GetLatestReadMarkerEntity(ctx context.Context, clientID string) (*ReadMarke } func SaveLatestReadMarker(ctx context.Context, rm *ReadMarker, isCreate bool) error { - var ( db = datastore.GetStore().GetTransaction(ctx) rmEntity = &ReadMarkerEntity{} diff --git a/code/go/0chain.net/blobbercore/stats/blobberstats.go b/code/go/0chain.net/blobbercore/stats/blobberstats.go index 080088a1b..5d4f1c111 100644 --- a/code/go/0chain.net/blobbercore/stats/blobberstats.go +++ b/code/go/0chain.net/blobbercore/stats/blobberstats.go @@ -42,7 +42,7 @@ type WriteMarkersStat struct { } type Stats struct { - TotalSize int64 `json:"total_size"` // the total allocated size + AllocatedSize int64 `json:"allocated_size"` UsedSize int64 `json:"used_size"` FilesSize int64 `json:"files_size"` ThumbnailsSize int64 `json:"thumbnails_size"` @@ -145,7 +145,7 @@ func (bs *BlobberStats) loadDetailedStats(ctx context.Context) { } given[as.AllocationID] = struct{}{} - bs.TotalSize += as.TotalSize + bs.AllocatedSize += as.AllocatedSize as.ReadMarkers, err = loadAllocReadMarkersStat(ctx, as.AllocationID) if err != nil { @@ -252,7 +252,7 @@ func (bs *BlobberStats) loadAllocationStats(ctx context.Context) { SUM(file_stats.num_of_block_downloads) as num_of_reads, SUM(reference_objects.num_of_blocks) as num_of_block_writes, COUNT(*) as num_of_writes, - allocations.size AS total_size, + allocations.size AS allocated_size, allocations.expiration_date AS expiration_date`). Joins(`INNER JOIN file_stats ON reference_objects.id = file_stats.ref_id`). @@ -262,6 +262,7 @@ func (bs *BlobberStats) loadAllocationStats(ctx context.Context) { Where(`reference_objects.type = 'f' AND reference_objects.deleted_at IS NULL`). Group(`reference_objects.allocation_id, allocations.expiration_date`). + Group(`reference_objects.allocation_id, allocations.size`). Rows() if err != nil { @@ -272,8 +273,8 @@ func (bs *BlobberStats) loadAllocationStats(ctx context.Context) { for rows.Next() { var as = &AllocationStats{} - err = rows.Scan(&as.AllocationID, &as.TotalSize, &as.FilesSize, &as.ThumbnailsSize, - &as.NumReads, &as.BlockWrites, &as.NumWrites, &as.Expiration) + err = rows.Scan(&as.AllocationID, &as.FilesSize, &as.ThumbnailsSize, + &as.NumReads, &as.BlockWrites, &as.NumWrites, &as.AllocatedSize, &as.Expiration) if err != nil { Logger.Error("Error in scanning record for blobber stats", zap.Error(err)) diff --git a/code/go/0chain.net/blobbercore/stats/handler.go b/code/go/0chain.net/blobbercore/stats/handler.go index aacd60049..faea3cdaa 100644 --- a/code/go/0chain.net/blobbercore/stats/handler.go +++ b/code/go/0chain.net/blobbercore/stats/handler.go @@ -65,7 +65,7 @@ const tpl = ` Allocated size (bytes) - {{ .TotalSize }} + {{ .AllocatedSize }} Used Size (bytes) diff --git a/code/go/0chain.net/core/common/handler.go b/code/go/0chain.net/core/common/handler.go index 71b933456..f84af02e6 100644 --- a/code/go/0chain.net/core/common/handler.go +++ b/code/go/0chain.net/core/common/handler.go @@ -39,6 +39,7 @@ type JSONReqResponderF func(ctx context.Context, json map[string]interface{}) (i /*Respond - respond either data or error as a response */ func Respond(w http.ResponseWriter, data interface{}, err error) { + w.Header().Set("Access-Control-Allow-Origin", "*") // CORS for all. w.Header().Set("Content-Type", "application/json") if err != nil { data := make(map[string]interface{}, 2) @@ -105,6 +106,7 @@ func SetupCORSResponse(w http.ResponseWriter, r *http.Request) { */ func ToJSONResponse(handler JSONResponderF) ReqRespHandlerf { return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") // CORS for all. if r.Method == "OPTIONS" { SetupCORSResponse(w, r) return diff --git a/code/go/0chain.net/core/encryption/keys.go b/code/go/0chain.net/core/encryption/keys.go index bda80893a..7000c2560 100644 --- a/code/go/0chain.net/core/encryption/keys.go +++ b/code/go/0chain.net/core/encryption/keys.go @@ -3,11 +3,14 @@ package encryption import ( "bufio" "io" + "strings" "0chain.net/core/common" "0chain.net/core/config" + . "0chain.net/core/logging" "github.com/0chain/gosdk/core/zcncrypto" + "github.com/herumi/bls-go-binary/bls" ) /*ReadKeys - reads a publicKey and a privateKey from a Reader. @@ -28,6 +31,8 @@ func ReadKeys(reader io.Reader) (publicKey string, privateKey string, publicIp s //Verify - given a public key and a signature and the hash used to create the signature, verify the signature func Verify(publicKey string, signature string, hash string) (bool, error) { + publicKey = MiraclToHerumiPK(publicKey) + signature = MiraclToHerumiSig(signature) signScheme := zcncrypto.NewSignatureScheme(config.Configuration.SignatureScheme) if signScheme != nil { err := signScheme.SetPublicKey(publicKey) @@ -38,3 +43,57 @@ func Verify(publicKey string, signature string, hash string) (bool, error) { } return false, common.NewError("invalid_signature_scheme", "Invalid signature scheme. Please check configuration") } + +// If input is normal herumi/bls public key, it returns it immmediately. +// So this is completely backward compatible with herumi/bls. +// If input is MIRACL public key, convert it to herumi/bls public key. +// +// This is an example of the raw public key we expect from MIRACL +var miraclExamplePK = `0418a02c6bd223ae0dfda1d2f9a3c81726ab436ce5e9d17c531ff0a385a13a0b491bdfed3a85690775ee35c61678957aaba7b1a1899438829f1dc94248d87ed36817f6dfafec19bfa87bf791a4d694f43fec227ae6f5a867490e30328cac05eaff039ac7dfc3364e851ebd2631ea6f1685609fc66d50223cc696cb59ff2fee47ac` +// +// This is an example of the same MIRACL public key serialized with ToString(). +// pk ([1bdfed3a85690775ee35c61678957aaba7b1a1899438829f1dc94248d87ed368,18a02c6bd223ae0dfda1d2f9a3c81726ab436ce5e9d17c531ff0a385a13a0b49],[039ac7dfc3364e851ebd2631ea6f1685609fc66d50223cc696cb59ff2fee47ac,17f6dfafec19bfa87bf791a4d694f43fec227ae6f5a867490e30328cac05eaff]) +func MiraclToHerumiPK(pk string) string { + if len(pk) != len(miraclExamplePK) { + // If input is normal herumi/bls public key, it returns it immmediately. + return pk + } + n1 := pk[2:66] + n2 := pk[66:(66+64)] + n3 := pk[(66+64):(66+64+64)] + n4 := pk[(66+64+64):(66+64+64+64)] + var p bls.PublicKey + err := p.SetHexString("1 " + n2 + " " + n1 + " " + n4 + " " + n3) + if err != nil { + Logger.Error("MiraclToHerumiPK: " + err.Error()) + } + return p.SerializeToHexStr() +} + +// Converts signature 'sig' to format that the herumi/bls library likes. +// zwallets are using MIRACL library which send a MIRACL signature not herumi +// lib. +// +// If the 'sig' was not in MIRACL format, we just return the original sig. +const miraclExampleSig = `(0d4dbad6d2586d5e01b6b7fbad77e4adfa81212c52b4a0b885e19c58e0944764,110061aa16d5ba36eef0ad4503be346908d3513c0a2aedfd0d2923411b420eca)` +func MiraclToHerumiSig(sig string) string { + if len(sig) <= 2 { + return sig + } + if sig[0] != miraclExampleSig[0] { + return sig + } + withoutParens := sig[1: (len(sig)-1) ] + comma := strings.Index(withoutParens, ",") + if comma < 0 { + return "00" + } + n1 := withoutParens[0:comma] + n2 := withoutParens[(comma+1):] + var sign bls.Sign + err := sign.SetHexString("1 " + n1 + " " + n2) + if err != nil { + Logger.Error("MiraclToHerumiSig: " + err.Error()) + } + return sign.SerializeToHexStr() +} diff --git a/code/go/0chain.net/core/encryption/keys_test.go b/code/go/0chain.net/core/encryption/keys_test.go index c8ee3d092..592dbe4e8 100644 --- a/code/go/0chain.net/core/encryption/keys_test.go +++ b/code/go/0chain.net/core/encryption/keys_test.go @@ -1,8 +1,10 @@ package encryption import ( + "encoding/hex" "github.com/0chain/gosdk/zboxcore/client" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" ) @@ -20,4 +22,58 @@ func TestSignatureVerify(t *testing.T) { ) assert.Nil(t, err) assert.Equal(t, res, true) +} + +func TestMiraclToHerumiPK(t *testing.T) { + miraclpk1 := `0418a02c6bd223ae0dfda1d2f9a3c81726ab436ce5e9d17c531ff0a385a13a0b491bdfed3a85690775ee35c61678957aaba7b1a1899438829f1dc94248d87ed36817f6dfafec19bfa87bf791a4d694f43fec227ae6f5a867490e30328cac05eaff039ac7dfc3364e851ebd2631ea6f1685609fc66d50223cc696cb59ff2fee47ac` + pk1 := MiraclToHerumiPK(miraclpk1) + + require.EqualValues(t, pk1, "68d37ed84842c91d9f82389489a1b1a7ab7a957816c635ee750769853aeddf1b490b3aa185a3f01f537cd1e9e56c43ab2617c8a3f9d2a1fd0dae23d26b2ca018") + + // Assert DeserializeHexStr works on the output of MiraclToHerumiPK + var pk bls.PublicKey + err := pk.DeserializeHexStr(pk1) + require.NoError(t, err) +} + +func TestMiraclToHerumiSig(t *testing.T) { + miraclsig1 := `(0d4dbad6d2586d5e01b6b7fbad77e4adfa81212c52b4a0b885e19c58e0944764,110061aa16d5ba36eef0ad4503be346908d3513c0a2aedfd0d2923411b420eca)` + sig1 := MiraclToHerumiSig(miraclsig1) + + require.EqualValues(t, sig1, "644794e0589ce185b8a0b4522c2181faade477adfbb7b6015e6d58d2d6ba4d0d") + + // Assert DeserializeHexStr works on the output of MiraclToHerumiSig + var sig bls.Sign + err := sig.DeserializeHexStr(sig1) + require.NoError(t, err) + + // Test that passing in normal herumi sig just gets back the original. + sig2 := MiraclToHerumiSig(sig1) + if sig1 != sig2 { + panic("Signatures should be the same.") + } +} + +// Helper code to print out expected values of Hash and conversion functions. +func TestDebugOnly(t *testing.T) { + + // clientKey := "536d2ecfe5aab6c343e8c2e7ee9daa60c43eecc53f4b1c07a6cb2648d9e66c14f2e3fcd43875be40722992f56570fe3c751caacbc7d859b309c787f654bd5a97" + // // => 5c2fdfa03fc013cff0e4b716f0529b914e18fd2bc6cdfed49df13b6e3dc4684d + + clientKey := "0416c528570ce46eb83584cd604a9ed62644ef4f71a86587d57e4ab91953ff4699107374870799ad4550c4f3833cca2a4d5de75436d67caf89097f1e7d6d7de6d424cb5a08b9dca8957ea7c81a23d066b93a27500954cd29733149ec1f8a8abd540d08f9f81bb24b83ff27e24f173e639573e10a22ed7b0ca326a1aa9dc03e1eef" + // => bd3adcacc78ed4352931b138729986a07d2bf0e0a3bf2c885b37a9a0e649dd87 + // Looking for bd3adcacc78ed4352931b138729986a07d2bf0e0a3bf2c885b37a9a0e649dd87 + + clientKeyBytes, _ := hex.DecodeString(clientKey) + h := Hash(clientKeyBytes) + + fmt.Println("hash ", h) + + herumipk := MiraclToHerumiPK(clientKey) + fmt.Println("herumipk ", herumipk) + clientKeyBytes2, _ := hex.DecodeString(herumipk) + h = Hash(clientKeyBytes2) + fmt.Println("hash2 ", h) + + } \ No newline at end of file diff --git a/code/go/0chain.net/core/transaction/entity.go b/code/go/0chain.net/core/transaction/entity.go index 72f8eabe5..8be9e6c31 100644 --- a/code/go/0chain.net/core/transaction/entity.go +++ b/code/go/0chain.net/core/transaction/entity.go @@ -68,13 +68,19 @@ type StakePoolSettings struct { ServiceCharge float64 `json:"service_charge"` } +type StorageNodeGeolocation struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + type StorageNode struct { - ID string `json:"id"` - BaseURL string `json:"url"` - Terms Terms `json:"terms"` - Capacity int64 `json:"capacity"` - PublicKey string `json:"-"` - StakePoolSettings StakePoolSettings `json:"stake_pool_settings"` + ID string `json:"id"` + BaseURL string `json:"url"` + Geolocation StorageNodeGeolocation `json:"geolocation"` + Terms Terms `json:"terms"` + Capacity int64 `json:"capacity"` + PublicKey string `json:"-"` + StakePoolSettings StakePoolSettings `json:"stake_pool_settings"` } type BlobberAllocation struct { @@ -95,6 +101,7 @@ type StorageAllocation struct { Finalized bool `json:"finalized"` CCT time.Duration `json:"challenge_completion_time"` TimeUnit time.Duration `json:"time_unit"` + IsImmutable bool `json:"is_immutable"` } func (sa *StorageAllocation) Until() common.Timestamp { @@ -115,7 +122,6 @@ const ( READ_REDEEM = "read_redeem" CHALLENGE_RESPONSE = "challenge_response" BLOBBER_HEALTH_CHECK = "blobber_health_check" - UPDATE_BLOBBER_SETTINGS = "update_blobber_settings" FINALIZE_ALLOCATION = "finalize_allocation" ) diff --git a/code/go/0chain.net/go.mod b/code/go/0chain.net/go.mod index ef3b0dd34..8bdf88b70 100644 --- a/code/go/0chain.net/go.mod +++ b/code/go/0chain.net/go.mod @@ -9,6 +9,7 @@ require ( github.com/gorilla/mux v1.7.3 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 + github.com/herumi/bls-go-binary v0.0.0-20191119080710-898950e1a520 // indirect github.com/jackc/pgproto3/v2 v2.0.4 // indirect github.com/koding/cache v0.0.0-20161222233015-e8a81b0b3f20 github.com/minio/minio-go v6.0.14+incompatible diff --git a/code/go/0chain.net/validatorcore/storage/context.go b/code/go/0chain.net/validatorcore/storage/context.go index 79a977074..2a761fe4a 100644 --- a/code/go/0chain.net/validatorcore/storage/context.go +++ b/code/go/0chain.net/validatorcore/storage/context.go @@ -10,7 +10,8 @@ import ( func SetupContext(handler common.JSONResponderF) common.JSONResponderF { return func(ctx context.Context, r *http.Request) (interface{}, error) { ctx = context.WithValue(ctx, CLIENT_CONTEXT_KEY, r.Header.Get(common.ClientHeader)) - ctx = context.WithValue(ctx, CLIENT_KEY_CONTEXT_KEY, r.Header.Get(common.ClientKeyHeader)) + ctx = context.WithValue(ctx, CLIENT_KEY_CONTEXT_KEY, + r.Header.Get(common.ClientKeyHeader)) res, err := handler(ctx, r) return res, err } diff --git a/config/0chain_blobber.yaml b/config/0chain_blobber.yaml index 73201116e..70a5fbc51 100755 --- a/config/0chain_blobber.yaml +++ b/config/0chain_blobber.yaml @@ -87,6 +87,10 @@ db: host: postgres port: 5432 +geolocation: + latitude: 0 + longitude: 0 + minio: # Enable or disable minio backup service start: false diff --git a/docker.local/b0docker-compose.yml b/docker.local/b0docker-compose.yml index 6030b1930..af8ba85c1 100644 --- a/docker.local/b0docker-compose.yml +++ b/docker.local/b0docker-compose.yml @@ -62,7 +62,7 @@ services: ports: - "505${BLOBBER}:505${BLOBBER}" - "703${BLOBBER}:703${BLOBBER}" - command: ./bin/blobber --port 505${BLOBBER} --grpc_port 703${BLOBBER} --hostname localhost --deployment_mode 0 --keys_file keysconfig/b0bnode${BLOBBER}_keys.txt --files_dir /blobber/files --log_dir /blobber/log --db_dir /blobber/data --minio_file keysconfig/minio_config.txt + command: ./bin/blobber --port 505${BLOBBER} --grpc_port 703${BLOBBER} --hostname 198.18.0.9${BLOBBER} --deployment_mode 0 --keys_file keysconfig/b0bnode${BLOBBER}_keys.txt --files_dir /blobber/files --log_dir /blobber/log --db_dir /blobber/data --minio_file keysconfig/minio_config.txt networks: default: testnet0: diff --git a/docker.local/bin/build.blobber-integration-tests.sh b/docker.local/bin/build.blobber-integration-tests.sh index 69b76eff7..895d5974c 100755 --- a/docker.local/bin/build.blobber-integration-tests.sh +++ b/docker.local/bin/build.blobber-integration-tests.sh @@ -4,8 +4,21 @@ set -e GIT_COMMIT=$(git rev-list -1 HEAD) echo $GIT_COMMIT -docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/ValidatorDockerfile . -t validator -docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/IntegrationTestsBlobberDockerfile . -t blobber +cmd="build" + +for arg in "$@" +do + case $arg in + -m1|--m1|m1) + echo "The build will be performed for Apple M1 chip" + cmd="buildx build --platform linux/amd64" + shift + ;; + esac +done + +docker $cmd --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/ValidatorDockerfile . -t validator +docker $cmd --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/IntegrationTestsBlobberDockerfile . -t blobber for i in $(seq 1 6); do diff --git a/docker.local/bin/build.blobber.sh b/docker.local/bin/build.blobber.sh index 6b27f854c..4cd4fda82 100755 --- a/docker.local/bin/build.blobber.sh +++ b/docker.local/bin/build.blobber.sh @@ -4,8 +4,21 @@ set -e GIT_COMMIT=$(git rev-list -1 HEAD) echo $GIT_COMMIT -docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/ValidatorDockerfile . -t validator -docker build --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/Dockerfile . -t blobber +cmd="build" + +for arg in "$@" +do + case $arg in + -m1|--m1|m1) + echo "The build will be performed for Apple M1 chip" + cmd="buildx build --platform linux/amd64" + shift + ;; + esac +done + +docker $cmd --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/ValidatorDockerfile . -t validator +docker $cmd --build-arg GIT_COMMIT=$GIT_COMMIT -f docker.local/Dockerfile . -t blobber for i in $(seq 1 6); do diff --git a/docs/cicd/CICD_GITACTIONS.md b/docs/cicd/CICD_GITACTIONS.md new file mode 100644 index 000000000..3aecae196 --- /dev/null +++ b/docs/cicd/CICD_GITACTIONS.md @@ -0,0 +1,128 @@ + + +## Guide to CI/CD using github actions + +## Workflow Creation. + - A new workflow is created using Go project with the file name called "build.yml". + - By default the path of build.yml is ".github/workflows.build.yml" + - Completed or running CI/CD can be seen under actions option. + + +## Details of components being used in build.yml. +#### Workflow name +Here the name of the workflow is defined i.e. "Dockerize" +``` +name: Dockerize +``` + +#### Input Option to trigger manually builds +To run the workflow using manual option, *work_dispatch* is used. Which will ask for the input to tigger the builds with *latest* tag or not. If we select for **yes**, image will be build with *latest* tag as well as with *branch-commitid* tag. But if we select for **no**, image will be build with *branch-commitid* tag only. + +``` +on: + workflow_dispatch: + inputs: + latest_tag: + description: 'type yes for building latest tag' + default: 'no' + required: true +``` + +#### Global ENV setup +Environment variable is defined with the secrets added to the repository. Here secrets contains the docker images(example- dockerhub) repository name. +``` +env: + BLOBBER_REGISTRY: ${{ secrets.BLOBBER_REGISTRY }} + VALIDATOR_REGISTRY: ${{ secrets.VALIDATOR_REGISTRY }} +``` + +#### Defining jobs and runner +Jobs are defined which contains the various steps for creating and pushing the builds. Runner envionment is also defined used for making the builds. +``` +jobs: + dockerize_blobber: + runs-on: ubuntu-20.04 + ... + dockerize_validator: + runs-on: ubuntu-20.04 +``` + +#### Different steps used in creating the builds +Here different steps are defined used for creating the builds. + - *uses* --> checkout to branch from what code to create the builds. + - *Get the version* --> Creating the tags by combining the branch name & first 8 digits of commit id. + - *Login to Docker Hub* --> Logging into the docker hub using Username and Password from secrets of the repository. + - *Build blobber/validator* --> Building, tagging and pushing the docker images with the *Get the version* tag. + - *Push blobber/validator* --> Here we are checking if the input given by user is **yes**, images is also pushed with latest tag also. + +For Blobber +``` +steps: +- uses: actions/checkout@v2 + +- name: Get the version + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} + +- name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + +- name: Build blobber + run: | + docker build -t $BLOBBER_REGISTRY:$TAG -f "$DOCKERFILE_BLOB" . + docker tag $BLOBBER_REGISTRY:$TAG $BLOBBER_REGISTRY:latest + ocker push $BLOBBER_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_BLOB: "docker.local/Dockerfile" + +- name: Push blobber + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $BLOBBER_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} +``` +For Validator +``` +steps: +- uses: actions/checkout@v1 + +- name: Get the version + id: get_version + run: | + BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g') + SHORT_SHA=$(echo $GITHUB_SHA | head -c 8) + echo ::set-output name=BRANCH::${BRANCH} + echo ::set-output name=VERSION::${BRANCH}-${SHORT_SHA} +- name: Login to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + +- name: Build validator + run: | + docker build -t $VALIDATOR_REGISTRY:$TAG -f "$DOCKERFILE_PROXY" . + docker tag $VALIDATOR_REGISTRY:$TAG $VALIDATOR_REGISTRY:latest + docker push $VALIDATOR_REGISTRY:$TAG + env: + TAG: ${{ steps.get_version.outputs.VERSION }} + DOCKERFILE_PROXY: "docker.local/ValidatorDockerfile" + +- name: Push validator + run: | + if [[ "$PUSH_LATEST" == "yes" ]]; then + docker push $VALIDATOR_REGISTRY:latest + fi + env: + PUSH_LATEST: ${{ github.event.inputs.latest_tag }} +``` diff --git a/docs/cicd/blobber.png b/docs/cicd/blobber.png new file mode 100644 index 0000000000000000000000000000000000000000..14364278ec5d1f0b5dcb8850281288390f76b2ff GIT binary patch literal 54031 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 qzJCMv9f5=YdB@22U`LEb4v4s~ah$dB0;IZ literal 0 HcmV?d00001 diff --git a/docs/src/repair.plantuml b/docs/src/repair.plantuml index 94009b9b9..ffde5ea64 100644 --- a/docs/src/repair.plantuml +++ b/docs/src/repair.plantuml @@ -1,6 +1,6 @@ @startuml actor Client -actor Payer +actor Repairer group partial upload Client -> Blobber : List command on a directory diff --git a/sql/14-increase_owner_pubkey.sql b/sql/14-increase_owner_pubkey.sql new file mode 100644 index 000000000..f6cd40520 --- /dev/null +++ b/sql/14-increase_owner_pubkey.sql @@ -0,0 +1,16 @@ +-- +-- Increase the char limit of owner_public_key from 256 to 512. +-- + +-- pew-pew +\connect blobber_meta; + +-- in a transaction +BEGIN; + ALTER TABLE allocations + ALTER COLUMN owner_public_key TYPE varchar(512); + ALTER TABLE read_markers + ALTER COLUMN client_public_key TYPE varchar(512); + ALTER TABLE write_markers + ALTER COLUMN client_key TYPE varchar(512); +COMMIT; diff --git a/sql/15-add-allocation-columns.sql b/sql/15-add-allocation-columns.sql new file mode 100644 index 000000000..c983cf73f --- /dev/null +++ b/sql/15-add-allocation-columns.sql @@ -0,0 +1,6 @@ +\connect blobber_meta; + +BEGIN; + ALTER TABLE allocations ADD COLUMN repairer_id VARCHAR(64) NOT NULL; + ALTER TABLE allocations ADD COLUMN is_immutable BOOLEAN NOT NULL; +COMMIT; \ No newline at end of file From 0acfdc720742fa908c3d76009a28882a26cfbf14 Mon Sep 17 00:00:00 2001 From: Uk Date: Tue, 6 Jul 2021 23:01:32 +0545 Subject: [PATCH 58/66] Replace gosdk version --- code/go/0chain.net/go.mod | 4 ++-- code/go/0chain.net/go.sum | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/code/go/0chain.net/go.mod b/code/go/0chain.net/go.mod index 8bdf88b70..f6e01cca6 100644 --- a/code/go/0chain.net/go.mod +++ b/code/go/0chain.net/go.mod @@ -1,7 +1,7 @@ module 0chain.net require ( - github.com/0chain/gosdk v1.1.6 + github.com/0chain/gosdk marketplace github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/didip/tollbooth v4.0.2+incompatible github.com/go-ini/ini v1.55.0 // indirect @@ -9,7 +9,7 @@ require ( github.com/gorilla/mux v1.7.3 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.3.0 - github.com/herumi/bls-go-binary v0.0.0-20191119080710-898950e1a520 // indirect + github.com/herumi/bls-go-binary v0.0.0-20191119080710-898950e1a520 github.com/jackc/pgproto3/v2 v2.0.4 // indirect github.com/koding/cache v0.0.0-20161222233015-e8a81b0b3f20 github.com/minio/minio-go v6.0.14+incompatible diff --git a/code/go/0chain.net/go.sum b/code/go/0chain.net/go.sum index a0ecd3327..dec840020 100644 --- a/code/go/0chain.net/go.sum +++ b/code/go/0chain.net/go.sum @@ -334,7 +334,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -350,7 +349,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 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= @@ -400,7 +398,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v1.0.1-0.20201006035406-b97b5ead31f7/go.mod h1:yk5b0mALVusDL5fMM6Rd1wgnoO5jUPhwsQ6LQAJTidQ= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -412,7 +409,6 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 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= @@ -446,22 +442,18 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= @@ -475,7 +467,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -500,7 +491,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -513,7 +503,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= @@ -614,7 +603,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 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.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= @@ -657,7 +645,6 @@ golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa h1:5E4dL8+NgFOgjwbTKz+OOEGGhP+ectTmF842l6KjupQ= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -681,7 +668,6 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -778,7 +764,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10= gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -790,7 +775,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -818,7 +802,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= From fbc9fd77ddd6c4b52d3b7ff3695faafe99a6650f Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 7 Jul 2021 18:17:57 +0545 Subject: [PATCH 59/66] Update sql version --- ...{14-add-marketplace-table.sql => 16-add-marketplace-table.sql} | 0 ...erence-objects.sql => 17-add-indexes-to-reference-objects.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename sql/{14-add-marketplace-table.sql => 16-add-marketplace-table.sql} (100%) rename sql/{15-add-indexes-to-reference-objects.sql => 17-add-indexes-to-reference-objects.sql} (100%) diff --git a/sql/14-add-marketplace-table.sql b/sql/16-add-marketplace-table.sql similarity index 100% rename from sql/14-add-marketplace-table.sql rename to sql/16-add-marketplace-table.sql diff --git a/sql/15-add-indexes-to-reference-objects.sql b/sql/17-add-indexes-to-reference-objects.sql similarity index 100% rename from sql/15-add-indexes-to-reference-objects.sql rename to sql/17-add-indexes-to-reference-objects.sql From 4518c892bee85e20fb4f929538c7ffc8bdd19014 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 7 Jul 2021 19:27:29 +0545 Subject: [PATCH 60/66] Force authticket check when provided --- .../0chain.net/blobbercore/handler/object_operation_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 5f43e3946..8db11078e 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -293,7 +293,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( var authToken *readmarker.AuthTicket = nil - if !isOwner && !isRepairer && !isCollaborator { + if (!isOwner && !isRepairer && !isCollaborator) || len(r.FormValue("auth_token")) > 0 { var authTokenString = r.FormValue("auth_token") // check auth token From 09a1e09fcb28b2ce1f59c59c73f790dfbe0d6d60 Mon Sep 17 00:00:00 2001 From: Uk Date: Wed, 7 Jul 2021 20:24:21 +0545 Subject: [PATCH 61/66] Fix dummy db connection snippet --- .../go/0chain.net/blobbercore/reference/ds_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/code/go/0chain.net/blobbercore/reference/ds_test.go b/code/go/0chain.net/blobbercore/reference/ds_test.go index caca35bb3..fac5f588f 100644 --- a/code/go/0chain.net/blobbercore/reference/ds_test.go +++ b/code/go/0chain.net/blobbercore/reference/ds_test.go @@ -3,10 +3,10 @@ package reference import ( "0chain.net/blobbercore/config" "0chain.net/blobbercore/datastore" - "fmt" "testing" ) +// this is just a dummy snippet to connect to local database func TestMockDb(t *testing.T) { config.Configuration.DBHost = "localhost" config.Configuration.DBName = "blobber_meta" @@ -16,11 +16,17 @@ func TestMockDb(t *testing.T) { datastore.GetStore().Open() db := datastore.GetStore().GetDB() + if db == nil { + t.Log("err connecting to database") + return + } ref := &Ref{} - err := db.Where(&Ref{AllocationID: "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367", Path: "/hack.txt"}).First(ref).Error + err := db.Where(&Ref{AllocationID: "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367", Path: "/hack.txt"}). + First(ref). + Error if err != nil { - fmt.Println("err", err) + t.Log("err", err) return } - fmt.Println(string(ref.Attributes)) + t.Log(string(ref.Attributes)) } From d546f66334bb603dd62ed814d83d83b2dee04d7b Mon Sep 17 00:00:00 2001 From: Piers Shepperson Date: Sat, 17 Jul 2021 10:13:48 +0100 Subject: [PATCH 62/66] fix lint issues --- .../blobbercore/filestore/fs_store.go | 17 ++-- .../blobbercore/handler/handler_test.go | 89 ++++++++++--------- .../handler/object_operation_handler.go | 22 +++-- .../blobbercore/reference/ds_test.go | 3 +- .../0chain.net/core/encryption/keys_test.go | 13 ++- 5 files changed, 74 insertions(+), 70 deletions(-) diff --git a/code/go/0chain.net/blobbercore/filestore/fs_store.go b/code/go/0chain.net/blobbercore/filestore/fs_store.go index 1ab9be0de..94eb5a484 100644 --- a/code/go/0chain.net/blobbercore/filestore/fs_store.go +++ b/code/go/0chain.net/blobbercore/filestore/fs_store.go @@ -102,8 +102,8 @@ func (FileBlockGetter) GetFileBlock(fs *FileFSStore, allocationID string, fileDa } type FileFSStore struct { - RootDirectory string - Minio *minio.Client + RootDirectory string + Minio *minio.Client fileBlockGetter IFileBlockGetter } @@ -118,18 +118,13 @@ func SetupFSStore(rootDir string) (FileStore, error) { if err := createDirs(rootDir); err != nil { return nil, err } - fsStore = &FileFSStore{ - RootDirectory: rootDir, - Minio: intializeMinio(), - fileBlockGetter: FileBlockGetter{}, - } - return fsStore, nil + return SetupFSStoreI(rootDir, FileBlockGetter{}) } -func SetupMockFSStore(rootDir string, fileBlockGetter IFileBlockGetter) (FileStore, error) { +func SetupFSStoreI(rootDir string, fileBlockGetter IFileBlockGetter) (FileStore, error) { fsStore = &FileFSStore{ - RootDirectory: rootDir, - Minio: intializeMinio(), + RootDirectory: rootDir, + Minio: intializeMinio(), fileBlockGetter: fileBlockGetter, } return fsStore, nil diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index e077ae950..4aab1c37d 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -24,6 +24,7 @@ import ( "github.com/DATA-DOG/go-sqlmock" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/gorm" "io" @@ -65,7 +66,9 @@ var encscheme zencryption.EncryptionScheme func setupEncryptionScheme() { encscheme = zencryption.NewEncryptionScheme() mnemonic := client.GetClient().Mnemonic - encscheme.Initialize(mnemonic) + if err := encscheme.Initialize(mnemonic); err != nil { + panic("initialize encscheme") + } encscheme.InitForEncryption("filetype:audio") } @@ -77,7 +80,7 @@ func init() { logging.Logger = zap.NewNop() dir, _ := os.Getwd() - if _, err := filestore.SetupMockFSStore(dir + "/tmp", MockFileBlockGetter{}); err != nil { + if _, err := filestore.SetupFSStoreI(dir+"/tmp", MockFileBlockGetter{}); err != nil { panic(err) } bconfig.Configuration.MaxFileSize = int64(1 << 30) @@ -227,20 +230,19 @@ func setupHandlers() (*mux.Router, map[string]string) { ), ).Name(shareName) - return router, map[string]string{ - opPath: opName, - rpPath: rpName, - sPath: sName, - otPath: otName, - collPath: collName, - rPath: rName, - cPath: cName, - aPath: aName, - uPath: uName, + opPath: opName, + rpPath: rpName, + sPath: sName, + otPath: otName, + collPath: collName, + rPath: rName, + cPath: cName, + aPath: aName, + uPath: uName, sharePath: shareName, - dPath: dName, + dPath: dName, } } @@ -286,12 +288,11 @@ func GetAuthTicketForEncryptedFile(allocationID string, remotePath string, fileH return string(atBytes), nil } - func TestHandlers_Requiring_Signature(t *testing.T) { setup(t) clientJson := "{\"client_id\":\"2f34516ed8c567089b7b5572b12950db34a62a07e16770da14b15b170d0d60a9\",\"client_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"keys\":[{\"public_key\":\"bc94452950dd733de3b4498afdab30ff72741beae0b82de12b80a14430018a09ba119ff0bfe69b2a872bded33d560b58c89e071cef6ec8388268d4c3e2865083\",\"private_key\":\"9fef6ff5edc39a79c1d8e5eb7ca7e5ac14d34615ee49e6d8ca12ecec136f5907\"}],\"mnemonics\":\"expose culture dignity plastic digital couple promote best pool error brush upgrade correct art become lobster nature moment obtain trial multiply arch miss toe\",\"version\":\"1.0\",\"date_created\":\"2021-05-30 17:45:06.492093 +0545 +0545 m=+0.139083805\"}" - client.PopulateClient(clientJson, "bls0chain") + require.NoError(t, client.PopulateClient(clientJson, "bls0chain")) setupEncryptionScheme() router, handlers := setupHandlers() @@ -322,8 +323,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { args args alloc *allocation.Allocation setupDbMock func(mock sqlmock.Sqlmock) - begin func() - end func() + begin func() + end func() wantCode int wantBody string } @@ -1125,14 +1126,14 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) shareClientEncryptionPublicKey := "kkk" shareClientID := "abcdefgh" - formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey) + require.NoError(t, formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey)) remotePath := "/file.txt" filePathHash := "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c" authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, filePathHash, shareClientID, sch.GetPublicKey()) if err != nil { t.Fatal(err) } - formWriter.WriteField("auth_ticket", authTicket) + require.NoError(t, formWriter.WriteField("auth_ticket", authTicket)) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1196,7 +1197,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { WillReturnResult(sqlmock.NewResult(0, 0)) }, wantCode: http.StatusOK, - wantBody: "{\"message\":\"Share info added successfully\"}\n", + wantBody: "{\"message\":\"Share info added successfully\"}\n", }, { name: "UpdateShareInfo", @@ -1213,14 +1214,14 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) shareClientEncryptionPublicKey := "kkk" shareClientID := "abcdefgh" - formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey) + require.NoError(t, formWriter.WriteField("encryption_public_key", shareClientEncryptionPublicKey)) remotePath := "/file.txt" filePathHash := "f15383a1130bd2fae1e52a7a15c432269eeb7def555f1f8b9b9a28bd9611362c" authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, filePathHash, shareClientID, sch.GetPublicKey()) if err != nil { t.Fatal(err) } - formWriter.WriteField("auth_ticket", authTicket) + require.NoError(t, formWriter.WriteField("auth_ticket", authTicket)) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1287,7 +1288,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { WillReturnResult(sqlmock.NewResult(0, 1)) }, wantCode: http.StatusOK, - wantBody: "{\"message\":\"Share info added successfully\"}\n", + wantBody: "{\"message\":\"Share info added successfully\"}\n", }, { name: "RevokeShareInfo_OK_Existing_Share", @@ -1305,8 +1306,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { shareClientID := "abcdefgh" remotePath := "/file.txt" - formWriter.WriteField("refereeClientID", shareClientID) - formWriter.WriteField("path", remotePath) + require.NoError(t, formWriter.WriteField("refereeClientID", shareClientID)) + require.NoError(t, formWriter.WriteField("path", remotePath)) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1385,8 +1386,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { shareClientID := "abcdefgh" remotePath := "/file.txt" - formWriter.WriteField("refereeClientID", shareClientID) - formWriter.WriteField("path", remotePath) + require.NoError(t, formWriter.WriteField("refereeClientID", shareClientID)) + require.NoError(t, formWriter.WriteField("path", remotePath)) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1464,8 +1465,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) remotePath := "/file.txt" - formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) - formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + require.NoError(t, formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath))) + require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1477,7 +1478,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { t.Fatal(err) } rmData, err := json.Marshal(rm) - formWriter.WriteField("read_marker", string(rmData)) + require.NoError(t, err) + require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1548,8 +1550,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { formWriter := multipart.NewWriter(body) remotePath := "/file.txt" - formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath)) - formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + require.NoError(t, formWriter.WriteField("path_hash", fileref.GetReferenceLookup(alloc.Tx, remotePath))) + require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1562,7 +1564,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { t.Fatal(err) } rmData, err := json.Marshal(rm) - formWriter.WriteField("read_marker", string(rmData)) + require.NoError(t, err) + require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1656,13 +1659,13 @@ func TestHandlers_Requiring_Signature(t *testing.T) { remotePath := "/file.txt" pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - formWriter.WriteField("path_hash", pathHash) - formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + require.NoError(t, formWriter.WriteField("path_hash", pathHash)) + require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, pathHash, client.GetClientID(), sch.GetPublicKey()) if err != nil { t.Fatal(err) } - formWriter.WriteField("auth_token", authTicket) + require.NoError(t, formWriter.WriteField("auth_token", authTicket)) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1675,7 +1678,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { t.Fatal(err) } rmData, err := json.Marshal(rm) - formWriter.WriteField("read_marker", string(rmData)) + require.NoError(t, err) + require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1774,13 +1778,13 @@ func TestHandlers_Requiring_Signature(t *testing.T) { remotePath := "/file.txt" pathHash := fileref.GetReferenceLookup(alloc.Tx, remotePath) - formWriter.WriteField("path_hash", pathHash) - formWriter.WriteField("block_num", fmt.Sprintf("%d", 1)) + require.NoError(t, formWriter.WriteField("path_hash", pathHash)) + require.NoError(t, formWriter.WriteField("block_num", fmt.Sprintf("%d", 1))) authTicket, err := GetAuthTicketForEncryptedFile(alloc.ID, remotePath, pathHash, client.GetClientID(), sch.GetPublicKey()) if err != nil { t.Fatal(err) } - formWriter.WriteField("auth_token", authTicket) + require.NoError(t, formWriter.WriteField("auth_token", authTicket)) rm := &marker.ReadMarker{} rm.ClientID = client.GetClientID() rm.ClientPublicKey = client.GetClientPublicKey() @@ -1793,7 +1797,8 @@ func TestHandlers_Requiring_Signature(t *testing.T) { t.Fatal(err) } rmData, err := json.Marshal(rm) - formWriter.WriteField("read_marker", string(rmData)) + require.NoError(t, err) + require.NoError(t, formWriter.WriteField("read_marker", string(rmData))) if err := formWriter.Close(); err != nil { t.Fatal(err) } @@ -1825,7 +1830,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { t.Fatal(err) } header := make([]byte, 2*1024) - copy(header[:], encMsg.MessageChecksum+ "," + encMsg.OverallChecksum) + copy(header[:], encMsg.MessageChecksum+","+encMsg.OverallChecksum) data := append(header, encMsg.EncryptedData...) setMockFileBlock(data) }, diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 8db11078e..f0c765e2c 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -286,14 +286,14 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( // authorize file access var ( - isOwner = clientID == alloc.OwnerID - isRepairer = clientID == alloc.RepairerID - isCollaborator = reference.IsACollaborator(ctx, fileref.ID, clientID) + isOwner = clientID == alloc.OwnerID + isRepairer = clientID == alloc.RepairerID + isCollaborator = reference.IsACollaborator(ctx, fileref.ID, clientID) ) var authToken *readmarker.AuthTicket = nil - if (!isOwner && !isRepairer && !isCollaborator) || len(r.FormValue("auth_token")) > 0 { + if (!isOwner && !isRepairer && !isCollaborator) || len(r.FormValue("auth_token")) > 0 { var authTokenString = r.FormValue("auth_token") // check auth token @@ -433,7 +433,7 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( readMarker.ClientID, authToken.FilePathHash, ) - if err != nil { + if err != nil { return nil, errors.New("error during share info lookup in database" + err.Error()) } else if shareInfo == nil || shareInfo.Revoked { return nil, errors.New("client does not have permission to download the file. share does not exist") @@ -444,17 +444,21 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( // reEncrypt does not require pub / private key, // we could probably make it a classless function - encscheme.Initialize("") - encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey) + if err := encscheme.Initialize(""); err != nil { + return nil, err + } + if err := encscheme.InitForDecryption("filetype:audio", fileref.EncryptedKey); err != nil { + return nil, err + } if err != nil { return nil, err } totalSize := len(respData) - result := []byte {} + result := []byte{} for i := 0; i < totalSize; i += reference.CHUNK_SIZE { encMsg := &zencryption.EncryptedMessage{} - chunkData := respData[i : int64(math.Min(float64(i + reference.CHUNK_SIZE), float64(totalSize)))] + chunkData := respData[i:int64(math.Min(float64(i+reference.CHUNK_SIZE), float64(totalSize)))] encMsg.EncryptedData = chunkData[(2 * 1024):] diff --git a/code/go/0chain.net/blobbercore/reference/ds_test.go b/code/go/0chain.net/blobbercore/reference/ds_test.go index fac5f588f..d491075c1 100644 --- a/code/go/0chain.net/blobbercore/reference/ds_test.go +++ b/code/go/0chain.net/blobbercore/reference/ds_test.go @@ -3,6 +3,7 @@ package reference import ( "0chain.net/blobbercore/config" "0chain.net/blobbercore/datastore" + "github.com/stretchr/testify/require" "testing" ) @@ -14,7 +15,7 @@ func TestMockDb(t *testing.T) { config.Configuration.DBUserName = "blobber_user" config.Configuration.DBPassword = "" - datastore.GetStore().Open() + require.NoError(t, datastore.GetStore().Open()) db := datastore.GetStore().GetDB() if db == nil { t.Log("err connecting to database") diff --git a/code/go/0chain.net/core/encryption/keys_test.go b/code/go/0chain.net/core/encryption/keys_test.go index cea2e598b..4eb0d206e 100644 --- a/code/go/0chain.net/core/encryption/keys_test.go +++ b/code/go/0chain.net/core/encryption/keys_test.go @@ -3,10 +3,9 @@ package encryption import ( "encoding/hex" "github.com/0chain/gosdk/zboxcore/client" - "github.com/stretchr/testify/assert" + "github.com/herumi/bls-go-binary/bls" "github.com/stretchr/testify/require" "testing" - "github.com/herumi/bls-go-binary/bls" "fmt" ) @@ -14,17 +13,17 @@ import ( func TestSignatureVerify(t *testing.T) { allocationId := "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367" walletConfig := "{\"client_id\":\"9a566aa4f8e8c342fed97c8928040a21f21b8f574e5782c28568635ba9c75a85\",\"client_key\":\"40cd10039913ceabacf05a7c60e1ad69bb2964987bc50f77495e514dc451f907c3d8ebcdab20eedde9c8f39b9a1d66609a637352f318552fb69d4b3672516d1a\",\"keys\":[{\"public_key\":\"40cd10039913ceabacf05a7c60e1ad69bb2964987bc50f77495e514dc451f907c3d8ebcdab20eedde9c8f39b9a1d66609a637352f318552fb69d4b3672516d1a\",\"private_key\":\"a3a88aad5d89cec28c6e37c2925560ce160ac14d2cdcf4a4654b2bb358fe7514\"}],\"mnemonics\":\"inside february piece turkey offer merry select combine tissue wave wet shift room afraid december gown mean brick speak grant gain become toy clown\",\"version\":\"1.0\",\"date_created\":\"2021-05-21 17:32:29.484657 +0545 +0545 m=+0.072791323\"}" - client.PopulateClient(walletConfig, "bls0chain") + require.NoError(t, client.PopulateClient(walletConfig, "bls0chain")) sig, serr := client.Sign(allocationId) - assert.Nil(t, serr) - assert.NotNil(t, sig) + require.Nil(t, serr) + require.NotNil(t, sig) res, err := client.VerifySignature( "fb0eb9351978091da350348211888b06ed1ce84ae40d08de3cc826cd85197188", allocationId, ) - assert.Nil(t, err) - assert.Equal(t, res, true) + require.Nil(t, err) + require.Equal(t, res, true) } func TestMiraclToHerumiPK(t *testing.T) { From 0ecaaca905a75b8ae6185098149468d1eed5edee Mon Sep 17 00:00:00 2001 From: Piers Shepperson Date: Sat, 17 Jul 2021 11:10:35 +0100 Subject: [PATCH 63/66] fix tests, wrap hidden error --- code/go/0chain.net/blobbercore/datastore/store.go | 6 +++--- code/go/0chain.net/blobbercore/errors/errors.go | 10 ---------- code/go/0chain.net/blobbercore/reference/ds_test.go | 7 ++----- .../go/0chain.net/validatorcore/storage/models_test.go | 3 +-- 4 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 code/go/0chain.net/blobbercore/errors/errors.go diff --git a/code/go/0chain.net/blobbercore/datastore/store.go b/code/go/0chain.net/blobbercore/datastore/store.go index 6f4de4f3f..a3925b8ee 100644 --- a/code/go/0chain.net/blobbercore/datastore/store.go +++ b/code/go/0chain.net/blobbercore/datastore/store.go @@ -1,12 +1,12 @@ package datastore import ( + "0chain.net/core/common" "context" "fmt" "time" "0chain.net/blobbercore/config" - "0chain.net/blobbercore/errors" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -39,12 +39,12 @@ func (store *Store) Open() error { config.Configuration.DBUserName, config.Configuration.DBName, config.Configuration.DBPassword)), &gorm.Config{}) if err != nil { - return errors.DBOpenError + return common.NewErrorf("db_open_error", "Error opening the DB connection: %v", err) } sqldb, err := db.DB() if err != nil { - return errors.DBOpenError + return common.NewErrorf("db_open_error", "Error opening the DB connection: %v", err) } sqldb.SetMaxIdleConns(100) diff --git a/code/go/0chain.net/blobbercore/errors/errors.go b/code/go/0chain.net/blobbercore/errors/errors.go deleted file mode 100644 index 9c40236d5..000000000 --- a/code/go/0chain.net/blobbercore/errors/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package errors - -import ( - "0chain.net/core/common" -) - -var ( - //DBOpenError - Error opening the db - DBOpenError = common.NewError("db_open_error", "Error opening the DB connection") -) diff --git a/code/go/0chain.net/blobbercore/reference/ds_test.go b/code/go/0chain.net/blobbercore/reference/ds_test.go index d491075c1..7e86cfbba 100644 --- a/code/go/0chain.net/blobbercore/reference/ds_test.go +++ b/code/go/0chain.net/blobbercore/reference/ds_test.go @@ -9,6 +9,7 @@ import ( // this is just a dummy snippet to connect to local database func TestMockDb(t *testing.T) { + t.Skip("Fails as the data store is not mocked, so Open returns a dial error") config.Configuration.DBHost = "localhost" config.Configuration.DBName = "blobber_meta" config.Configuration.DBPort = "5431" @@ -25,9 +26,5 @@ func TestMockDb(t *testing.T) { err := db.Where(&Ref{AllocationID: "4f928c7857fabb5737347c42204eea919a4777f893f35724f563b932f64e2367", Path: "/hack.txt"}). First(ref). Error - if err != nil { - t.Log("err", err) - return - } - t.Log(string(ref.Attributes)) + require.NoError(t, err) } diff --git a/code/go/0chain.net/validatorcore/storage/models_test.go b/code/go/0chain.net/validatorcore/storage/models_test.go index 6a12fbd88..9fc012184 100644 --- a/code/go/0chain.net/validatorcore/storage/models_test.go +++ b/code/go/0chain.net/validatorcore/storage/models_test.go @@ -622,8 +622,7 @@ func TestObjectPath_VerifyPath(t *testing.T) { if !tt.wantErr { require.NoError(t, err) } else { - t.Log(err) - assert.Contains(t, err.Error(), tt.wantErrMsg) + require.Contains(t, err.Error(), tt.wantErrMsg) } }) } From b70716330b118547c2a8df056ae6994736373d3c Mon Sep 17 00:00:00 2001 From: Piers Shepperson Date: Sat, 17 Jul 2021 12:07:12 +0100 Subject: [PATCH 64/66] merge issues --- .../0chain.net/blobbercore/datastore/store.go | 6 +- .../0chain.net/blobbercore/handler/handler.go | 60 +++++++++++-------- .../blobbercore/handler/handler_test.go | 24 +++----- .../handler/object_operation_handler.go | 35 ++++------- .../blobbercore/reference/ds_test.go | 7 ++- .../blobbercore/reference/shareinfo.go | 29 +++++---- 6 files changed, 75 insertions(+), 86 deletions(-) diff --git a/code/go/0chain.net/blobbercore/datastore/store.go b/code/go/0chain.net/blobbercore/datastore/store.go index 23fef18fe..529a3016a 100644 --- a/code/go/0chain.net/blobbercore/datastore/store.go +++ b/code/go/0chain.net/blobbercore/datastore/store.go @@ -1,15 +1,13 @@ package datastore import ( - "0chain.net/core/common" "context" "fmt" "time" - "0chain.net/blobbercore/config" - "github.com/0chain/blobber/code/go/0chain.net/blobbercore/config" - "github.com/0chain/blobber/code/go/0chain.net/blobbercore/errors" + "github.com/0chain/blobber/code/go/0chain.net/core/common" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/config" "gorm.io/driver/postgres" "gorm.io/gorm" diff --git a/code/go/0chain.net/blobbercore/handler/handler.go b/code/go/0chain.net/blobbercore/handler/handler.go index 64e5a0e96..c2404b3d6 100644 --- a/code/go/0chain.net/blobbercore/handler/handler.go +++ b/code/go/0chain.net/blobbercore/handler/handler.go @@ -3,18 +3,19 @@ package handler import ( - "0chain.net/blobbercore/readmarker" - "0chain.net/blobbercore/reference" "context" "encoding/json" "errors" - "github.com/0chain/gosdk/zboxcore/fileref" - "gorm.io/gorm" "net/http" "os" "runtime/pprof" "time" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/readmarker" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference" + "github.com/0chain/gosdk/zboxcore/fileref" + "gorm.io/gorm" + "go.uber.org/zap" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/config" @@ -345,10 +346,15 @@ func RevokeShare(ctx context.Context, r *http.Request) (interface{}, error) { allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) if err != nil { - return nil, common.NewError("invalid_parameters", "Invalid allocation id passed." + err.Error()) + return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + sign := r.Header.Get(common.ClientSignatureHeader) + allocation, ok := mux.Vars(r)["allocation"] + if !ok { + return false, common.NewError("invalid_params", "Missing allocation tx") + } + valid, err := verifySignatureFromRequest(allocation, sign, allocationObj.OwnerPublicKey) if !valid || err != nil { return nil, common.NewError("invalid_signature", "Invalid signature") } @@ -358,19 +364,19 @@ func RevokeShare(ctx context.Context, r *http.Request) (interface{}, error) { filePathHash := fileref.GetReferenceLookup(allocationID, path) _, err = reference.GetReferenceFromLookupHash(ctx, allocationID, filePathHash) if err != nil { - return nil, common.NewError("invalid_parameters", "Invalid file path. " + err.Error()) + return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } clientID := ctx.Value(constants.CLIENT_CONTEXT_KEY).(string) if clientID != allocationObj.OwnerID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner of the allocation") } - err = reference.DeleteShareInfo(ctx, reference.ShareInfo { - ClientID: refereeClientID, + err = reference.DeleteShareInfo(ctx, reference.ShareInfo{ + ClientID: refereeClientID, FilePathHash: filePathHash, }) if errors.Is(err, gorm.ErrRecordNotFound) { - resp := map[string]interface{} { - "status": http.StatusNotFound, + resp := map[string]interface{}{ + "status": http.StatusNotFound, "message": "Path not found", } return resp, nil @@ -378,8 +384,8 @@ func RevokeShare(ctx context.Context, r *http.Request) (interface{}, error) { if err != nil { return nil, err } - resp := map[string]interface{} { - "status": http.StatusNoContent, + resp := map[string]interface{}{ + "status": http.StatusNoContent, "message": "Path successfully removed from allocation", } return resp, nil @@ -391,10 +397,15 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { allocationID := ctx.Value(constants.ALLOCATION_CONTEXT_KEY).(string) allocationObj, err := storageHandler.verifyAllocation(ctx, allocationID, true) if err != nil { - return nil, common.NewError("invalid_parameters", "Invalid allocation id passed." + err.Error()) + return nil, common.NewError("invalid_parameters", "Invalid allocation id passed."+err.Error()) } - valid, err := verifySignatureFromRequest(r, allocationObj.OwnerPublicKey) + sign := r.Header.Get(common.ClientSignatureHeader) + allocation, ok := mux.Vars(r)["allocation"] + if !ok { + return false, common.NewError("invalid_params", "Missing allocation tx") + } + valid, err := verifySignatureFromRequest(allocation, sign, allocationObj.OwnerPublicKey) if !valid || err != nil { return nil, common.NewError("invalid_signature", "Invalid signature") } @@ -405,17 +416,17 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { err = json.Unmarshal([]byte(authTicketString), &authTicket) if err != nil { - return false, common.NewError("invalid_parameters", "Error parsing the auth ticket for download." + err.Error()) + return false, common.NewError("invalid_parameters", "Error parsing the auth ticket for download."+err.Error()) } fileref, err := reference.GetReferenceFromLookupHash(ctx, allocationID, authTicket.FilePathHash) if err != nil { - return nil, common.NewError("invalid_parameters", "Invalid file path. " + err.Error()) + return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } authTicketVerified, err := storageHandler.verifyAuthTicket(ctx, authTicketString, allocationObj, fileref, authTicket.ClientID) if !authTicketVerified { - return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket. " + err.Error()) + return nil, common.NewError("auth_ticket_verification_failed", "Could not verify the auth ticket. "+err.Error()) } if err != nil { @@ -423,12 +434,12 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { } shareInfo := reference.ShareInfo{ - OwnerID: authTicket.OwnerID, - ClientID: authTicket.ClientID, - FilePathHash: authTicket.FilePathHash, - ReEncryptionKey: authTicket.ReEncryptionKey, + OwnerID: authTicket.OwnerID, + ClientID: authTicket.ClientID, + FilePathHash: authTicket.FilePathHash, + ReEncryptionKey: authTicket.ReEncryptionKey, ClientEncryptionPublicKey: encryptionPublicKey, - ExpiryAt: common.ToTime(authTicket.Expiration), + ExpiryAt: common.ToTime(authTicket.Expiration), } existingShare, err := reference.GetShareInfo(ctx, authTicket.ClientID, authTicket.FilePathHash) @@ -445,7 +456,7 @@ func InsertShare(ctx context.Context, r *http.Request) (interface{}, error) { return nil, err } - resp := map[string]interface{} { + resp := map[string]interface{}{ "message": "Share info added successfully", } @@ -463,4 +474,3 @@ func MarketPlaceShareInfoHandler(ctx context.Context, r *http.Request) (interfac return nil, errors.New("invalid request method, only POST is allowed") } - diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index a0c0fa4c6..de830a258 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -5,6 +5,15 @@ import ( "encoding/json" "errors" "fmt" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "regexp" + "testing" + "time" + "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/client" zencryption "github.com/0chain/gosdk/zboxcore/encryption" @@ -17,14 +26,6 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/gorm" - "io" - "mime/multipart" - "net/http" - "net/http/httptest" - "os" - "regexp" - "testing" - "time" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation" bconfig "github.com/0chain/blobber/code/go/0chain.net/blobbercore/config" @@ -36,13 +37,6 @@ import ( "github.com/0chain/blobber/code/go/0chain.net/core/config" "github.com/0chain/blobber/code/go/0chain.net/core/encryption" "github.com/0chain/blobber/code/go/0chain.net/core/logging" - "github.com/0chain/gosdk/core/zcncrypto" - "github.com/0chain/gosdk/zcncore" - "github.com/DATA-DOG/go-sqlmock" - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "go.uber.org/zap" - "gorm.io/gorm" ) type MockFileBlockGetter struct { diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 44995067a..9e1147afe 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -1,7 +1,6 @@ package handler import ( - "0chain.net/blobbercore/util" "bytes" "context" "encoding/hex" @@ -11,6 +10,9 @@ import ( "strings" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/blobberhttp" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/stats" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/util" + zencryption "github.com/0chain/gosdk/zboxcore/encryption" "net/http" "path/filepath" @@ -23,27 +25,11 @@ import ( "github.com/0chain/blobber/code/go/0chain.net/blobbercore/filestore" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/readmarker" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference" - "github.com/0chain/blobber/code/go/0chain.net/blobbercore/stats" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/writemarker" - "github.com/0chain/blobber/code/go/0chain.net/core/common" "github.com/0chain/blobber/code/go/0chain.net/core/encryption" "github.com/0chain/blobber/code/go/0chain.net/core/lock" "github.com/0chain/blobber/code/go/0chain.net/core/node" - "0chain.net/blobbercore/allocation" - "0chain.net/blobbercore/config" - "0chain.net/blobbercore/constants" - "0chain.net/blobbercore/datastore" - "0chain.net/blobbercore/filestore" - "0chain.net/blobbercore/readmarker" - "0chain.net/blobbercore/reference" - "0chain.net/blobbercore/stats" - "0chain.net/blobbercore/writemarker" - "0chain.net/core/common" - "0chain.net/core/encryption" - "0chain.net/core/lock" - "0chain.net/core/node" - zencryption "github.com/0chain/gosdk/zboxcore/encryption" zfileref "github.com/0chain/gosdk/zboxcore/fileref" "gorm.io/datatypes" @@ -192,9 +178,10 @@ func writePreRedeem(ctx context.Context, alloc *allocation.Allocation, return } -func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( - resp interface{}, err error) { - +func (fsh *StorageHandler) DownloadFile( + ctx context.Context, + r *http.Request, +) (resp interface{}, err error) { if r.Method == "GET" { return nil, common.NewError("download_file", "invalid method used (GET), use POST instead") @@ -321,7 +308,6 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( return nil, common.NewErrorf("download_file", "error parsing the auth ticket for download: %v", err) } - } // set payer: check for command line payer flag (--rx_pay) if r.FormValue("rx_pay") == "true" { payerID = clientID @@ -441,9 +427,6 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( "couldn't save latest read marker: %v", err) } - var response = &blobberhttp.DownloadResponse{} - response.Success = true - response.LatestRM = readMarker if len(fileref.EncryptedKey) > 0 { if authToken == nil { return nil, errors.New("auth ticket is required to download encrypted file") @@ -509,9 +492,13 @@ func (fsh *StorageHandler) DownloadFile(ctx context.Context, r *http.Request) ( } respData = result } + var response = &blobberhttp.DownloadResponse{} + response.Success = true + response.LatestRM = readMarker response.Data = respData response.Path = fileref.Path response.AllocationID = fileref.AllocationID + response = response stats.FileBlockDownloaded(ctx, fileref.ID) return respData, nil diff --git a/code/go/0chain.net/blobbercore/reference/ds_test.go b/code/go/0chain.net/blobbercore/reference/ds_test.go index 7e86cfbba..06bb793d8 100644 --- a/code/go/0chain.net/blobbercore/reference/ds_test.go +++ b/code/go/0chain.net/blobbercore/reference/ds_test.go @@ -1,10 +1,11 @@ package reference import ( - "0chain.net/blobbercore/config" - "0chain.net/blobbercore/datastore" - "github.com/stretchr/testify/require" "testing" + + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/config" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore" + "github.com/stretchr/testify/require" ) // this is just a dummy snippet to connect to local database diff --git a/code/go/0chain.net/blobbercore/reference/shareinfo.go b/code/go/0chain.net/blobbercore/reference/shareinfo.go index c006c5b33..f0ab62ec6 100644 --- a/code/go/0chain.net/blobbercore/reference/shareinfo.go +++ b/code/go/0chain.net/blobbercore/reference/shareinfo.go @@ -1,21 +1,21 @@ package reference import ( - "0chain.net/blobbercore/datastore" "context" - "gorm.io/gorm" "time" -) + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/datastore" + "gorm.io/gorm" +) type ShareInfo struct { - OwnerID string `gorm:"owner_id" json:"owner_id,omitempty"` - ClientID string `gorm:"client_id" json:"client_id"` - FilePathHash string `gorm:"file_path_hash" json:"file_path_hash,omitempty"` - ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` - ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` - Revoked bool `gorm:"revoked" json:"revoked"` - ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` + OwnerID string `gorm:"owner_id" json:"owner_id,omitempty"` + ClientID string `gorm:"client_id" json:"client_id"` + FilePathHash string `gorm:"file_path_hash" json:"file_path_hash,omitempty"` + ReEncryptionKey string `gorm:"re_encryption_key" json:"re_encryption_key,omitempty"` + ClientEncryptionPublicKey string `gorm:"client_encryption_public_key" json:"client_encryption_public_key,omitempty"` + Revoked bool `gorm:"revoked" json:"revoked"` + ExpiryAt time.Time `gorm:"expiry_at" json:"expiry_at,omitempty"` } func TableName() string { @@ -33,9 +33,9 @@ func DeleteShareInfo(ctx context.Context, shareInfo ShareInfo) error { result := db.Table(TableName()). Where(&ShareInfo{ - ClientID: shareInfo.ClientID, + ClientID: shareInfo.ClientID, FilePathHash: shareInfo.FilePathHash, - Revoked: false, + Revoked: false, }). Updates(ShareInfo{ Revoked: true, @@ -56,7 +56,7 @@ func UpdateShareInfo(ctx context.Context, shareInfo ShareInfo) error { return db.Table(TableName()). Where(&ShareInfo{ - ClientID: shareInfo.ClientID, + ClientID: shareInfo.ClientID, FilePathHash: shareInfo.FilePathHash, }). Select("Revoked", "ReEncryptionKey", "ExpiryAt", "ClientEncryptionPublicKey"). @@ -69,7 +69,7 @@ func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*S shareInfo := &ShareInfo{} err := db.Table(TableName()). Where(&ShareInfo{ - ClientID: clientID, + ClientID: clientID, FilePathHash: filePathHash, }). First(shareInfo).Error @@ -82,4 +82,3 @@ func GetShareInfo(ctx context.Context, clientID string, filePathHash string) (*S } return shareInfo, nil } - From e1ac6180a853e03382443a803e75238841ca7cfb Mon Sep 17 00:00:00 2001 From: Piers Shepperson Date: Sat, 17 Jul 2021 12:18:47 +0100 Subject: [PATCH 65/66] merge issues --- code/go/0chain.net/blobbercore/errors/errors.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 code/go/0chain.net/blobbercore/errors/errors.go diff --git a/code/go/0chain.net/blobbercore/errors/errors.go b/code/go/0chain.net/blobbercore/errors/errors.go deleted file mode 100644 index e69de29bb..000000000 From c15c51306ae09ca108980f4b2c028414ba8a532b Mon Sep 17 00:00:00 2001 From: Piers Shepperson Date: Sat, 17 Jul 2021 12:35:40 +0100 Subject: [PATCH 66/66] merge issues --- .../blobbercore/handler/object_operation_handler.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index 9e1147afe..29f0e499f 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -492,13 +492,6 @@ func (fsh *StorageHandler) DownloadFile( } respData = result } - var response = &blobberhttp.DownloadResponse{} - response.Success = true - response.LatestRM = readMarker - response.Data = respData - response.Path = fileref.Path - response.AllocationID = fileref.AllocationID - response = response stats.FileBlockDownloaded(ctx, fileref.ID) return respData, nil