diff --git a/core/conf/conf.go b/core/conf/conf.go new file mode 100644 index 000000000..d1897e5b8 --- /dev/null +++ b/core/conf/conf.go @@ -0,0 +1,23 @@ +// pakcage conf provide config helpers for ~/.zcn/config.yaml, ~/.zcn/network.yaml and ~/.zcn/wallet.json + +package conf + +import ( + "errors" +) + +var ( + // ErrMssingConfig config file is missing + ErrMssingConfig = errors.New("[conf]missing config file") + // ErrInvalidValue invalid value in config + ErrInvalidValue = errors.New("[conf]invalid value") + // ErrBadParsing fail to parse config via spf13/viper + ErrBadParsing = errors.New("[conf]bad parsing") +) + +// Reader a config reader +type Reader interface { + GetString(key string) string + GetInt(key string) int + GetStringSlice(key string) []string +} diff --git a/core/conf/config.go b/core/conf/config.go new file mode 100644 index 000000000..c92100ad3 --- /dev/null +++ b/core/conf/config.go @@ -0,0 +1,151 @@ +package conf + +import ( + "errors" + "net/url" + "os" + "strings" + + thrown "github.com/0chain/errors" + "github.com/spf13/viper" +) + +const ( + // DefaultMinSubmit default value for min_submit + DefaultMinSubmit = 50 + // DefaultMinConfirmation default value for min_confirmation + DefaultMinConfirmation = 50 + // DefaultMaxTxnQuery default value for max_txn_query + DefaultMaxTxnQuery = 5 + // DefaultConfirmationChainLength default value for confirmation_chain_length + DefaultConfirmationChainLength = 3 + // DefaultQuerySleepTime default value for query_sleep_time + DefaultQuerySleepTime = 5 +) + +// Config settings from ~/.zcn/config.yaml +// block_worker: http://198.18.0.98:9091 +// signature_scheme: bls0chain +// min_submit: 50 +// min_confirmation: 50 +// confirmation_chain_length: 3 +// max_txn_query: 5 +// query_sleep_time: 5 +// # # OPTIONAL - Uncomment to use/ Add more if you want +// # preferred_blobbers: +// # - http://one.devnet-0chain.net:31051 +// # - http://one.devnet-0chain.net:31052 +// # - http://one.devnet-0chain.net:31053 +type Config struct { + // BlockWorker the url of 0dns's network api + BlockWorker string + // PreferredBlobbers preferred blobbers on new allocation + PreferredBlobbers []string + + // MinSubmit mininal submit from blobber + MinSubmit int + // MinConfirmation mininal confirmation from sharders + MinConfirmation int + // CconfirmationChainLength minial confirmation chain length + ConfirmationChainLength int + + // additional settings depending network latency + // MaxTxnQuery maximum transcation query from sharders + MaxTxnQuery int + // QuerySleepTime sleep time before transcation query + QuerySleepTime int + + // SignatureScheme signature scheme + SignatureScheme string + // ChainID which blockchain it is working + ChainID string +} + +// LoadConfigFile load and parse Config from file +func LoadConfigFile(file string) (Config, error) { + + var cfg Config + var err error + + _, err = os.Stat(file) + + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return cfg, thrown.Throw(ErrMssingConfig, file) + } + return cfg, err + } + + v := viper.New() + + v.SetConfigFile(file) + + if err := v.ReadInConfig(); err != nil { + return cfg, thrown.Throw(ErrBadParsing, err.Error()) + } + + return LoadConfig(v) +} + +// LoadConfig load and parse config +func LoadConfig(v Reader) (Config, error) { + + var cfg Config + + blockWorker := strings.TrimSpace(v.GetString("block_worker")) + + if !isURL(blockWorker) { + return cfg, thrown.Throw(ErrInvalidValue, "block_worker="+blockWorker) + } + + minSubmit := v.GetInt("min_submit") + if minSubmit < 1 { + minSubmit = DefaultMinSubmit + } else if minSubmit > 100 { + minSubmit = 100 + } + + minCfm := v.GetInt("min_confirmation") + + if minCfm < 1 { + minCfm = DefaultMinConfirmation + } else if minCfm > 100 { + minCfm = 100 + } + + CfmChainLength := v.GetInt("confirmation_chain_length") + + if CfmChainLength < 1 { + CfmChainLength = DefaultConfirmationChainLength + } + + // additional settings depending network latency + maxTxnQuery := v.GetInt("max_txn_query") + if maxTxnQuery < 1 { + maxTxnQuery = DefaultMaxTxnQuery + } + + querySleepTime := v.GetInt("query_sleep_time") + if querySleepTime < 1 { + querySleepTime = DefaultQuerySleepTime + } + + cfg.BlockWorker = blockWorker + cfg.PreferredBlobbers = v.GetStringSlice("preferred_blobbers") + cfg.MinSubmit = minSubmit + cfg.MinConfirmation = minCfm + cfg.ConfirmationChainLength = CfmChainLength + cfg.MaxTxnQuery = maxTxnQuery + cfg.QuerySleepTime = querySleepTime + + cfg.SignatureScheme = v.GetString("signature_scheme") + cfg.ChainID = v.GetString("chain_id") + + return cfg, nil + +} + +func isURL(s string) bool { + u, err := url.Parse(s) + return err == nil && u.Scheme != "" && u.Host != "" +} diff --git a/core/conf/config_test.go b/core/conf/config_test.go new file mode 100644 index 000000000..29d6a7587 --- /dev/null +++ b/core/conf/config_test.go @@ -0,0 +1,183 @@ +package conf + +import ( + "testing" + + "github.com/0chain/gosdk/core/conf/mocks" + "github.com/stretchr/testify/require" +) + +func TestLoadConfig(t *testing.T) { + + var mockDefaultReader = func() Reader { + reader := &mocks.Reader{} + reader.On("GetString", "block_worker").Return("http://127.0.0.1:9091/dns") + reader.On("GetInt", "min_submit").Return(0) + reader.On("GetInt", "min_confirmation").Return(0) + reader.On("GetInt", "max_txn_query").Return(0) + reader.On("GetInt", "query_sleep_time").Return(0) + reader.On("GetInt", "confirmation_chain_length").Return(0) + reader.On("GetStringSlice", "preferred_blobbers").Return(nil) + reader.On("GetString", "signature_scheme").Return("") + reader.On("GetString", "chain_id").Return("") + + return reader + + } + + tests := []struct { + name string + exceptedErr error + + setup func(*testing.T) Reader + run func(*require.Assertions, Config) + }{ + { + name: "Test_Config_Invalid_BlockWorker", + exceptedErr: ErrInvalidValue, + setup: func(t *testing.T) Reader { + + reader := &mocks.Reader{} + reader.On("GetString", "block_worker").Return("") + reader.On("GetInt", "min_submit").Return(0) + reader.On("GetInt", "min_confirmation").Return(0) + reader.On("GetInt", "max_txn_query").Return(0) + reader.On("GetInt", "query_sleep_time").Return(0) + reader.On("GetInt", "confirmation_chain_length").Return(0) + reader.On("GetStringSlice", "preferred_blobbers").Return(nil) + reader.On("GetString", "signature_scheme").Return("") + reader.On("GetString", "chain_id").Return("") + + return reader + }, + run: func(r *require.Assertions, cfg Config) { + + }, + }, + { + name: "Test_Config_BlockWorker", + + setup: func(t *testing.T) Reader { + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal("http://127.0.0.1:9091/dns", cfg.BlockWorker) + }, + }, + { + name: "Test_Config_Min_Submit_Less_Than_1", + + setup: func(t *testing.T) Reader { + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(50, cfg.MinSubmit) + }, + }, + { + name: "Test_Config_Min_Submit_Greater_Than_100", + + setup: func(t *testing.T) Reader { + + reader := &mocks.Reader{} + reader.On("GetString", "block_worker").Return("https://127.0.0.1:9091/dns") + reader.On("GetInt", "min_submit").Return(101) + reader.On("GetInt", "min_confirmation").Return(0) + reader.On("GetInt", "max_txn_query").Return(0) + reader.On("GetInt", "query_sleep_time").Return(0) + reader.On("GetInt", "confirmation_chain_length").Return(0) + reader.On("GetStringSlice", "preferred_blobbers").Return(nil) + reader.On("GetString", "signature_scheme").Return("") + reader.On("GetString", "chain_id").Return("") + + return reader + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(100, cfg.MinSubmit) + }, + }, + { + name: "Test_Config_Min_Confirmation_Less_Than_1", + + setup: func(t *testing.T) Reader { + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(50, cfg.MinConfirmation) + }, + }, + { + name: "Test_Config_Min_Confirmation_Greater_100", + + setup: func(t *testing.T) Reader { + + reader := &mocks.Reader{} + reader.On("GetString", "block_worker").Return("https://127.0.0.1:9091/dns") + reader.On("GetInt", "min_submit").Return(0) + reader.On("GetInt", "min_confirmation").Return(101) + reader.On("GetInt", "max_txn_query").Return(0) + reader.On("GetInt", "query_sleep_time").Return(0) + reader.On("GetInt", "confirmation_chain_length").Return(0) + reader.On("GetStringSlice", "preferred_blobbers").Return(nil) + reader.On("GetString", "signature_scheme").Return("") + reader.On("GetString", "chain_id").Return("") + + return reader + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(100, cfg.MinConfirmation) + }, + }, { + name: "Test_Config_Nax_Txn_Query_Less_Than_1", + + setup: func(t *testing.T) Reader { + + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(5, cfg.QuerySleepTime) + }, + }, { + name: "Test_Config_Max_Txn_Query_Less_Than_1", + + setup: func(t *testing.T) Reader { + + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(5, cfg.MaxTxnQuery) + }, + }, { + name: "Test_Config_Confirmation_Chain_Length_Less_Than_1", + + setup: func(t *testing.T) Reader { + return mockDefaultReader() + }, + run: func(r *require.Assertions, cfg Config) { + r.Equal(3, cfg.ConfirmationChainLength) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + require := require.New(t) + + reader := tt.setup(t) + + cfg, err := LoadConfig(reader) + + // test it by predefined error variable instead of error message + if tt.exceptedErr != nil { + require.ErrorIs(err, tt.exceptedErr) + } else { + require.Equal(nil, err) + } + + if tt.run != nil { + tt.run(require, cfg) + } + + }) + } +} diff --git a/core/conf/mocks/Reader.go b/core/conf/mocks/Reader.go new file mode 100644 index 000000000..9bb859362 --- /dev/null +++ b/core/conf/mocks/Reader.go @@ -0,0 +1,54 @@ +// Code generated by mockery 2.9.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// Reader is an autogenerated mock type for the Reader type +type Reader struct { + mock.Mock +} + +// GetInt provides a mock function with given fields: key +func (_m *Reader) GetInt(key string) int { + ret := _m.Called(key) + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(key) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// GetString provides a mock function with given fields: key +func (_m *Reader) GetString(key string) string { + ret := _m.Called(key) + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(key) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetStringSlice provides a mock function with given fields: key +func (_m *Reader) GetStringSlice(key string) []string { + ret := _m.Called(key) + + var r0 []string + if rf, ok := ret.Get(0).(func(string) []string); ok { + r0 = rf(key) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + return r0 +} diff --git a/core/conf/network.go b/core/conf/network.go new file mode 100644 index 000000000..8bd28e740 --- /dev/null +++ b/core/conf/network.go @@ -0,0 +1,56 @@ +package conf + +import ( + "errors" + "os" + + thrown "github.com/0chain/errors" + "github.com/spf13/viper" +) + +// Network settings from ~/.zcn/network.yaml +type Network struct { + // Sharders sharder list of blockchain + Sharders []string + // Miners miner list of blockchain + Miners []string +} + +// IsValid check network if it has miners and sharders +func (n *Network) IsValid() bool { + return n != nil && len(n.Miners) > 0 && len(n.Sharders) > 0 +} + +// LoadNetworkFile load and parse Network from file +func LoadNetworkFile(file string) (Network, error) { + + var network Network + var err error + + _, err = os.Stat(file) + + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return network, thrown.Throw(ErrMssingConfig, file) + } + return network, err + } + + v := viper.New() + + v.SetConfigFile(file) + + if err := v.ReadInConfig(); err != nil { + return network, thrown.Throw(ErrBadParsing, err.Error()) + } + + return LoadNetwork(v), nil +} + +// LoadNetwork load and parse network +func LoadNetwork(v Reader) Network { + return Network{ + Sharders: v.GetStringSlice("sharders"), + Miners: v.GetStringSlice("miners"), + } +} diff --git a/core/resty/mocks/Client.go b/core/resty/mocks/Client.go new file mode 100644 index 000000000..db64565a0 --- /dev/null +++ b/core/resty/mocks/Client.go @@ -0,0 +1,37 @@ +// Code generated by mockery 2.9.0. DO NOT EDIT. + +package mocks + +import ( + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +// Do provides a mock function with given fields: req +func (_m *Client) Do(req *http.Request) (*http.Response, error) { + ret := _m.Called(req) + + var r0 *http.Response + if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok { + r0 = rf(req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Response) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*http.Request) error); ok { + r1 = rf(req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/resty/mocks/timeout.go b/core/resty/mocks/timeout.go new file mode 100644 index 000000000..a14f100a3 --- /dev/null +++ b/core/resty/mocks/timeout.go @@ -0,0 +1,21 @@ +package mocks + +import ( + "context" + http "net/http" + "time" +) + +// Timeout mock any request with timeout +type Timeout struct { + Timeout time.Duration +} + +// Do provides a mock function with given fields: req +func (t *Timeout) Do(req *http.Request) (*http.Response, error) { + time.Sleep(t.Timeout) + + time.Sleep(1 * time.Second) + + return nil, context.DeadlineExceeded +} diff --git a/core/resty/option.go b/core/resty/option.go new file mode 100644 index 000000000..64f5a82c9 --- /dev/null +++ b/core/resty/option.go @@ -0,0 +1,32 @@ +package resty + +import ( + "time" +) + +// WithRetry set retry times if request is failure with 5xx status code. retry is ingore if it is less than 1. +func WithRetry(retry int) Option { + return func(r *Resty) { + if retry > 0 { + r.retry = retry + } + } +} + +// WithHeader set header for http request +func WithHeader(header map[string]string) Option { + return func(r *Resty) { + if len(header) > 0 { + r.header = header + } + } +} + +// WithTimeout set timeout of http request. +func WithTimeout(timeout time.Duration) Option { + return func(r *Resty) { + if timeout > 0 { + r.timeout = timeout + } + } +} diff --git a/core/resty/resty.go b/core/resty/resty.go new file mode 100644 index 000000000..33d288988 --- /dev/null +++ b/core/resty/resty.go @@ -0,0 +1,184 @@ +// Package resty HTTP and REST client library with parallel feature +package resty + +import ( + "context" + "net/http" + "time" +) + +// New create a Resty instance. +func New(transport *http.Transport, handle Handle, opts ...Option) *Resty { + r := &Resty{ + transport: transport, + handle: handle, + } + + for _, option := range opts { + option(r) + } + + if r.transport == nil { + r.transport = &http.Transport{} + } + + r.client = CreateClient(r.transport, r.timeout) + + return r +} + +// CreateClient a function that create a client instance +var CreateClient = func(t *http.Transport, timeout time.Duration) Client { + client := &http.Client{ + Transport: t, + } + if timeout > 0 { + client.Timeout = timeout + } + + return client +} + +// Client http client +type Client interface { + Do(req *http.Request) (*http.Response, error) +} + +// Handle handler of http response +type Handle func(req *http.Request, resp *http.Response, cf context.CancelFunc, err error) error + +// Option set restry option +type Option func(*Resty) + +// Resty HTTP and REST client library with parallel feature +type Resty struct { + ctx context.Context + cancelFunc context.CancelFunc + qty int + done chan Result + + transport *http.Transport + client Client + handle Handle + + timeout time.Duration + retry int + header map[string]string +} + +// DoGet execute http requests with GET method in parallel +func (r *Resty) DoGet(ctx context.Context, urls ...string) { + r.ctx, r.cancelFunc = context.WithCancel(ctx) + + r.qty = len(urls) + r.done = make(chan Result, r.qty) + + for _, url := range urls { + req, err := http.NewRequest(http.MethodGet, url, nil) + for key, value := range r.header { + req.Header.Set(key, value) + } + + req.Close = true + req.Header.Set("Connection", "close") + + if err != nil { + + r.done <- Result{Request: req, Response: nil, Err: err} + + continue + } + + go r.httpDo(req) + } + +} + +func (r *Resty) httpDo(req *http.Request) { + + ctx, cancel := context.WithCancel(r.ctx) + defer cancel() + + c := make(chan error, 1) + defer close(c) + + go func(req *http.Request) { + var resp *http.Response + var err error + + if r.retry > 0 { + for i := 1; ; i++ { + resp, err = r.client.Do(req) + if resp != nil && resp.StatusCode == 200 { + break + } + // close body ReadClose to release resource before retrying it + if resp != nil && resp.Body != nil { + // don't close it if it is latest retry + if i < r.retry { + resp.Body.Close() + } + } + + if i == r.retry { + break + } + } + } else { + resp, err = r.client.Do(req.WithContext(r.ctx)) + } + + r.done <- Result{Request: req, Response: resp, Err: err} + + c <- err + + }(req.WithContext(ctx)) + + select { + case <-ctx.Done(): + r.transport.CancelRequest(req) + <-c + return + case <-c: + return + } + +} + +// Wait wait all of requests to done +func (r *Resty) Wait() []error { + defer func() { + // call cancelFunc, aovid to memory leak issue + if r.cancelFunc != nil { + r.cancelFunc() + } + }() + + errs := make([]error, 0, r.qty) + done := 0 + + for { + + result := <-r.done + + if r.handle != nil { + err := r.handle(result.Request, result.Response, r.cancelFunc, result.Err) + + if err != nil { + errs = append(errs, err) + } + } else { + if result.Err != nil { + errs = append(errs, result.Err) + } + } + + done++ + + if done >= r.qty { + return errs + } + + } + +} diff --git a/core/resty/resty_test.go b/core/resty/resty_test.go new file mode 100644 index 000000000..61b852e34 --- /dev/null +++ b/core/resty/resty_test.go @@ -0,0 +1,118 @@ +package resty + +import ( + "context" + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "github.com/0chain/gosdk/core/resty/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestResty(t *testing.T) { + + tests := []struct { + name string + urls []string + + statusCode int + exceptedErr error + + setup func(a *require.Assertions, name string, statusCode int, urls []string) (context.Context, *Resty) + }{ + { + name: "Test_Resty_Timeout", + exceptedErr: context.DeadlineExceeded, + urls: []string{"Test_Resty_Timeout_1", "Test_Resty_Timeout_2", "Test_Resty_Timeout_3"}, + setup: func(ra *require.Assertions, name string, statusCode int, urls []string) (context.Context, *Resty) { + + r := New(&http.Transport{}, nil) + r.client = &mocks.Timeout{ + Timeout: 1 * time.Second, + } + + ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) + go func() { + <-ctx.Done() + cancel() + }() + + return ctx, r + }, + }, + { + name: "Test_Resty_All_Success", + statusCode: 200, + exceptedErr: context.DeadlineExceeded, + urls: []string{"http://Test_Resty_Timeout_1", "http://Test_Resty_Timeout_2"}, + setup: func(ra *require.Assertions, name string, statusCode int, urls []string) (context.Context, *Resty) { + + resty := New(&http.Transport{}, func(req *http.Request, resp *http.Response, cf context.CancelFunc, e error) error { + + ra.Equal(200, resp.StatusCode) + + buf, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + ra.Equal(nil, err) + ra.Equal(name, string(buf)) + + return nil + }) + + client := &mocks.Client{} + + for _, url := range urls { + + func(u string) { + client.On("Do", mock.MatchedBy(func(r *http.Request) bool { + return r.URL.String() == u + })).Return(&http.Response{ + StatusCode: statusCode, + Body: ioutil.NopCloser(strings.NewReader(name)), + }, nil) + }(url) + + } + + resty.client = client + + ctx, cancel := context.WithTimeout(context.TODO(), 3*time.Second) + go func() { + <-ctx.Done() + cancel() + }() + + return context.TODO(), resty + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + r := require.New(t) + + ctx, resty := tt.setup(r, tt.name, tt.statusCode, tt.urls) + + resty.DoGet(ctx, tt.urls...) + + errs := resty.Wait() + + for _, err := range errs { + // test it by predefined error variable instead of error message + if tt.exceptedErr != nil { + + r.ErrorIs(err, tt.exceptedErr) + } else { + r.Equal(nil, err) + } + } + + }) + } +} diff --git a/core/resty/result.go b/core/resty/result.go new file mode 100644 index 000000000..9fb1841bb --- /dev/null +++ b/core/resty/result.go @@ -0,0 +1,10 @@ +package resty + +import "net/http" + +// Result result of a http request +type Result struct { + Request *http.Request + Response *http.Response + Err error +} diff --git a/core/resty/vars.go b/core/resty/vars.go new file mode 100644 index 000000000..1bf97d019 --- /dev/null +++ b/core/resty/vars.go @@ -0,0 +1,12 @@ +package resty + +import "time" + +var ( + // DefaultDialTimeout default timeout of a dialer + DefaultDialTimeout = 5 * time.Second + // DefaultRequestTimeout default time out of a http request + DefaultRequestTimeout = 10 * time.Second + // DefaultRetry retry times if a request is failed with 5xx status code + DefaultRetry = 3 +) diff --git a/core/transaction/entity.go b/core/transaction/entity.go index 73c264b21..50568958f 100644 --- a/core/transaction/entity.go +++ b/core/transaction/entity.go @@ -1,21 +1,27 @@ package transaction import ( + "context" "encoding/json" "fmt" + "io/ioutil" + "math" + "net" + "net/http" + "strconv" + "strings" "sync" "github.com/0chain/errors" "github.com/0chain/gosdk/core/common" "github.com/0chain/gosdk/core/encryption" + "github.com/0chain/gosdk/core/resty" "github.com/0chain/gosdk/core/util" ) const TXN_SUBMIT_URL = "v1/transaction/put" const TXN_VERIFY_URL = "v1/transaction/get/confirmation?hash=" -var ErrNoTxnDetail = errors.New("missing_transaction_detail", "No transaction detail was found on any of the sharders") - //Transaction entity that encapsulates the transaction related data and meta data type Transaction struct { Hash string `json:"hash,omitempty"` @@ -204,61 +210,147 @@ func sendTransactionToURL(url string, txn *Transaction, wg *sync.WaitGroup) ([]b return nil, errors.Wrap(err, errors.New("transaction_send_error", postResponse.Body)) } +// VerifyTransaction query transaction status from sharders, and verify it by mininal confirmation func VerifyTransaction(txnHash string, sharders []string) (*Transaction, error) { + if cfg == nil { + return nil, ErrConfigIsNotInitialized + } + numSharders := len(sharders) + + if numSharders == 0 { + return nil, ErrNoAvailableSharder + } + + minNumConfirmation := int(math.Ceil(float64(cfg.MinConfirmation*numSharders) / 100)) + + rand := util.NewRand(numSharders) + + selectedSharders := make([]string, 0, minNumConfirmation+1) + + // random pick minNumConfirmation+1 first + for i := 0; i <= minNumConfirmation; i++ { + n, err := rand.Next() + + if err != nil { + break + } + + selectedSharders = append(selectedSharders, sharders[n]) + } + numSuccess := 0 + var retTxn *Transaction - var customError error - for _, sharder := range sharders { - url := fmt.Sprintf("%v/%v%v", sharder, TXN_VERIFY_URL, txnHash) - req, err := util.NewHTTPGetRequest(url) - if err != nil { - customError = errors.Wrap(customError, err) - numSharders-- - continue + + //leave first item for ErrTooLessConfirmation + var msgList = make([]string, 1, numSharders) + + urls := make([]string, 0, len(selectedSharders)) + + for _, sharder := range selectedSharders { + urls = append(urls, fmt.Sprintf("%v/%v%v", sharder, TXN_VERIFY_URL, txnHash)) + } + + header := map[string]string{ + "Content-Type": "application/json; charset=utf-8", + "Access-Control-Allow-Origin": "*", + } + + transport := &http.Transport{ + Dial: (&net.Dialer{ + Timeout: resty.DefaultDialTimeout, + }).Dial, + TLSHandshakeTimeout: resty.DefaultDialTimeout, + } + r := resty.New(transport, func(req *http.Request, resp *http.Response, cf context.CancelFunc, err error) error { + url := req.URL.String() + + if err != nil { //network issue + msgList = append(msgList, err.Error()) + return err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { //network issue + msgList = append(msgList, url+": "+err.Error()) + return err + } + + if resp.StatusCode != 200 { + msgList = append(msgList, url+": ["+strconv.Itoa(resp.StatusCode)+"] "+string(body)) + return errors.Throw(ErrInvalidRequest, strconv.Itoa(resp.StatusCode)+": "+resp.Status) } - response, err := req.Get() + + var objmap map[string]json.RawMessage + err = json.Unmarshal(body, &objmap) if err != nil { - customError = errors.Wrap(customError, err) - numSharders-- - continue - } else { - if response.StatusCode != 200 { - customError = errors.Wrap(customError, err) - continue - } - contents := response.Body - var objmap map[string]json.RawMessage - err = json.Unmarshal([]byte(contents), &objmap) - if err != nil { - customError = errors.Wrap(customError, err) - continue - } - if _, ok := objmap["txn"]; !ok { - if _, ok := objmap["block_hash"]; ok { - numSuccess++ - } else { - customError = errors.Wrap(customError, fmt.Sprintf("Sharder does not have the block summary with url: %s, contents: %s", url, contents)) - } - continue - } + msgList = append(msgList, "json: "+string(body)) + return err + } + txnRawJSON, ok := objmap["txn"] + + // txn data is found, success + if ok { txn := &Transaction{} - err = json.Unmarshal(objmap["txn"], txn) + err = json.Unmarshal(txnRawJSON, txn) if err != nil { - customError = errors.Wrap(customError, err) - continue + msgList = append(msgList, "json: "+string(txnRawJSON)) + return err } if len(txn.Signature) > 0 { retTxn = txn } numSuccess++ + + } else { + // txn data is not found, but get block_hash, success + if _, ok := objmap["block_hash"]; ok { + numSuccess++ + } else { + // txn and block_hash + msgList = append(msgList, fmt.Sprintf("Sharder does not have the block summary with url: %s, contents: %s", url, string(body))) + } + } + + return nil + }, + resty.WithTimeout(resty.DefaultRequestTimeout), + resty.WithRetry(resty.DefaultRetry), + resty.WithHeader(header)) + + for { + r.DoGet(context.TODO(), urls...) + + r.Wait() + + if numSuccess >= minNumConfirmation { + break + } + + // pick more one sharder to query transaction + n, err := rand.Next() + + if errors.Is(err, util.ErrNoItem) { + break + } + + urls = []string{fmt.Sprintf("%v/%v%v", sharders[n], TXN_VERIFY_URL, txnHash)} + } - if numSharders == 0 || float64(numSuccess*1.0/numSharders) > 0.5 { - if retTxn != nil { - return retTxn, nil + + if numSuccess > 0 && numSuccess >= minNumConfirmation { + + if retTxn == nil { + return nil, errors.Throw(ErrNoTxnDetail, strings.Join(msgList, "\r\n")) } - return nil, errors.Wrap(customError, ErrNoTxnDetail) + + return retTxn, nil } - return nil, errors.Wrap(customError, errors.New("transaction_not_found", "Transaction was not found on any of the sharders")) + + msgList[0] = fmt.Sprintf("min_confirmation is %v%%, but got %v/%v sharders", cfg.MinConfirmation, numSuccess, numSharders) + + return nil, errors.Throw(ErrTooLessConfirmation, strings.Join(msgList, "\r\n")) + } diff --git a/core/transaction/vars.go b/core/transaction/vars.go new file mode 100644 index 000000000..3949316bf --- /dev/null +++ b/core/transaction/vars.go @@ -0,0 +1,33 @@ +package transaction + +import ( + "errors" + + "github.com/0chain/gosdk/core/conf" +) + +var ( + // Config + cfg *conf.Config +) + +// SetConfig set config variables for transaction +func SetConfig(c *conf.Config) { + cfg = c +} + +var ( + ErrInvalidRequest = errors.New("[txn] invalid request") + + // ErrNoAvailableSharder no any available sharder + ErrNoAvailableSharder = errors.New("[txn] there is no any available sharder") + + // ErrNoTxnDetail No transaction detail was found on any of the sharders + ErrNoTxnDetail = errors.New("[txn] no transaction detail was found on any of the sharders") + + // ErrTooLessConfirmation too less sharder to confirm transaction + ErrTooLessConfirmation = errors.New("[txn] too less sharders to confirm it") + + // ErrConfigIsNotInitialized config is not initialized + ErrConfigIsNotInitialized = errors.New("[txn] config is not initialized. please initialize it by transaction.SetConfig") +) diff --git a/core/util/httpnet.go b/core/util/httpnet.go index 67093fec1..268428627 100644 --- a/core/util/httpnet.go +++ b/core/util/httpnet.go @@ -112,28 +112,37 @@ func httpDo(req *http.Request, ctx context.Context, cncl context.CancelFunc, f f } } +// NewHTTPGetRequest create a GetRequest instance with 60s timeout func NewHTTPGetRequest(url string) (*GetRequest, error) { - var ctx, _ = context.WithTimeout(context.Background(), 60*time.Second) + var ctx, cancel = context.WithTimeout(context.Background(), 60*time.Second) + go func() { + //call cancel to avoid memory leak here + <-ctx.Done() + + cancel() + + }() + return NewHTTPGetRequestContext(ctx, url) } -func NewHTTPGetRequestContext(ctx context.Context, url string) ( - gr *GetRequest, err error) { +// NewHTTPGetRequestContext create a GetRequest with context and url +func NewHTTPGetRequestContext(ctx context.Context, url string) (*GetRequest, error) { - var req *http.Request - if req, err = http.NewRequest(http.MethodGet, url, nil); err != nil { - return + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err } req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Access-Control-Allow-Origin", "*") - gr = new(GetRequest) + gr := new(GetRequest) gr.PostRequest = &PostRequest{} gr.url = url gr.req = req gr.ctx, gr.cncl = context.WithCancel(ctx) - return + return gr, nil } func NewHTTPPostRequest(url string, data interface{}) (*PostRequest, error) { diff --git a/core/util/merkle_tree.go b/core/util/merkle_tree.go index ba80cf5ec..f665bda2c 100644 --- a/core/util/merkle_tree.go +++ b/core/util/merkle_tree.go @@ -2,8 +2,6 @@ package util import ( "fmt" - - "github.com/0chain/errors" ) /*MerkleTree - A data structure that implements MerkleTreeI interface */ @@ -82,7 +80,7 @@ func (mt *MerkleTree) GetTree() []string { func (mt *MerkleTree) SetTree(leavesCount int, tree []string) error { size, levels := mt.computeSize(leavesCount) if size != len(tree) { - return errors.New(fmt.Sprintf("Merkle tree with leaves %v should have size %v but only %v is given", leavesCount, size, len(tree))) + return fmt.Errorf("Merkle tree with leaves %v should have size %v but only %v is given", leavesCount, size, len(tree)) } mt.levels = levels mt.tree = tree diff --git a/core/util/rand.go b/core/util/rand.go index d763ea3c0..f4076b9ff 100644 --- a/core/util/rand.go +++ b/core/util/rand.go @@ -1,6 +1,7 @@ package util import ( + "errors" "math/rand" "time" ) @@ -69,3 +70,46 @@ func GetRandom(in []string, n int) []string { } return out } + +var ( + randGen = rand.New(rand.NewSource(time.Now().UnixNano())) + // ErrNoItem there is no item anymore + ErrNoItem = errors.New("rand: there is no item anymore") +) + +// Rand a progressive rand +type Rand struct { + items []int +} + +// Next get next random item +func (r *Rand) Next() (int, error) { + it := -1 + if len(r.items) > 0 { + i := randGen.Intn(len(r.items)) + + it = r.items[i] + + copy(r.items[i:], r.items[i+1:]) + r.items = r.items[:len(r.items)-1] + + return it, nil + } + + return -1, ErrNoItem + +} + +// NewRand create a ProgressiveRand instance +func NewRand(max int) Rand { + r := Rand{ + items: make([]int, max), + } + + for i := 0; i < max; i++ { + r.items[i] = i + } + + return r + +} diff --git a/core/util/rand_test.go b/core/util/rand_test.go index d53fee4ce..e4379f515 100644 --- a/core/util/rand_test.go +++ b/core/util/rand_test.go @@ -2,6 +2,8 @@ package util import ( "testing" + + "github.com/stretchr/testify/require" ) func TestGetRandomSlice(t *testing.T) { @@ -15,3 +17,32 @@ func TestGetRandomSlice(t *testing.T) { t.Fatalf("Getrandom() failed") } } + +func TestRand(t *testing.T) { + s := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"} + + r := NewRand(len(s)) + + selected := make(map[int]string) + + for i := 0; i < len(s); i++ { + index, err := r.Next() + + require.Equal(t, nil, err) + + _, ok := selected[index] + + require.Equal(t, false, ok) + + selected[index] = s[index] + } + + for i := 0; i < len(s); i++ { + require.Equal(t, s[i], selected[i]) + } + + _, err := r.Next() + + require.Equal(t, ErrNoItem, err) + +} diff --git a/core/zcncrypto/ed255190chain.go b/core/zcncrypto/ed255190chain.go index 0652ec8fe..a7822d98d 100644 --- a/core/zcncrypto/ed255190chain.go +++ b/core/zcncrypto/ed255190chain.go @@ -59,7 +59,7 @@ func (ed *ED255190chainScheme) GenerateKeys() (*Wallet, error) { //GenerateKeysWithEth - not implemented func (ed *ED255190chainScheme) GenerateKeysWithEth(mnemonic, password string) (*Wallet, error) { - return nil, errors.New("Not supported for this scheme") + return nil, errors.New("", "Not supported for this scheme") } func (ed *ED255190chainScheme) RecoverKeys(mnemonic string) (*Wallet, error) { diff --git a/go.mod b/go.mod index e654401c6..16dd1b3fd 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/0chain/gosdk require ( - github.com/0chain/errors v1.0.1 // indirect + github.com/0chain/errors v1.0.2 github.com/ethereum/go-ethereum v1.10.3 github.com/h2non/filetype v1.0.9 github.com/herumi/bls-go-binary v0.0.0-20191119080710-898950e1a520 @@ -9,6 +9,7 @@ require ( github.com/lithammer/shortuuid/v3 v3.0.7 github.com/miguelmota/go-ethereum-hdwallet v0.0.1 github.com/mitchellh/mapstructure v1.1.2 + github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.7.0 github.com/tyler-smith/go-bip39 v1.1.0 go.dedis.ch/kyber/v3 v3.0.5 diff --git a/go.sum b/go.sum index c6e9a614e..333c54296 100644 --- a/go.sum +++ b/go.sum @@ -19,10 +19,8 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/0chain/errors v0.0.0-20210806215752-722ff90592d4 h1:eGD6i45mNntgqdFoIqqvi4TCjdyQEsSIUChOtt/SQ5M= -github.com/0chain/errors v0.0.0-20210806215752-722ff90592d4/go.mod h1:ZICwljXH1eFGj+V37kYDkgQBMkI/LL0JOIA5BuhYDMw= -github.com/0chain/errors v1.0.1 h1:e1jyKmYtF5jQdFOeEAjdfyLe5D46HrNO46Z97tWvEh8= -github.com/0chain/errors v1.0.1/go.mod h1:5t76jLb56TKfg/K2VD+eUMmNZJ42QsIRI8KzWuztwU4= +github.com/0chain/errors v1.0.2 h1:IIUMeh/qFlqDcyHesjU92CpRMVz9dIQWAtZooqrYinA= +github.com/0chain/errors v1.0.2/go.mod h1:5t76jLb56TKfg/K2VD+eUMmNZJ42QsIRI8KzWuztwU4= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= @@ -284,6 +282,7 @@ github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -325,6 +324,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -380,6 +380,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -425,6 +426,7 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o= @@ -512,6 +514,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +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/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= @@ -589,19 +592,26 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +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.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -621,6 +631,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= @@ -963,6 +974,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= diff --git a/zboxcore/encoder/erasurecode.go b/zboxcore/encoder/erasurecode.go index 9a11de1dc..3a58aa3d9 100644 --- a/zboxcore/encoder/erasurecode.go +++ b/zboxcore/encoder/erasurecode.go @@ -4,7 +4,8 @@ import ( "bufio" "bytes" - "github.com/0chain/errors" + "errors" + . "github.com/0chain/gosdk/zboxcore/logger" "github.com/klauspost/reedsolomon" diff --git a/zboxcore/encryption/pre.go b/zboxcore/encryption/pre.go index dd58ec0cf..41cb4cbe8 100644 --- a/zboxcore/encryption/pre.go +++ b/zboxcore/encryption/pre.go @@ -11,7 +11,8 @@ import ( "encoding/json" "strings" - "github.com/0chain/errors" + "errors" + "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/group/edwards25519" ) diff --git a/zboxcore/sdk/allocation.go b/zboxcore/sdk/allocation.go index 604251f4d..17d7f6c79 100644 --- a/zboxcore/sdk/allocation.go +++ b/zboxcore/sdk/allocation.go @@ -17,20 +17,19 @@ import ( "sync" "time" - "github.com/0chain/gosdk/zboxcore/client" - "github.com/0chain/gosdk/zboxcore/fileref" - "github.com/0chain/errors" "github.com/0chain/gosdk/core/common" "github.com/0chain/gosdk/core/transaction" "github.com/0chain/gosdk/zboxcore/blockchain" + "github.com/0chain/gosdk/zboxcore/client" + "github.com/0chain/gosdk/zboxcore/fileref" . "github.com/0chain/gosdk/zboxcore/logger" "github.com/0chain/gosdk/zboxcore/marker" "github.com/0chain/gosdk/zboxcore/zboxutil" ) var ( - noBLOBBERS = errors.New("No Blobbers set in this allocation") + noBLOBBERS = errors.New("", "No Blobbers set in this allocation") notInitialized = errors.New("sdk_not_initialized", "Please call InitStorageSDK Init and use GetAllocation to get the allocation object") ) @@ -415,7 +414,7 @@ func (a *Allocation) uploadOrUpdateFile(localpath string, } if !repairRequired { - return errors.New("Repair not required") + return errors.New("", "Repair not required") } file, _ := ioutil.ReadFile(localpath) @@ -423,7 +422,7 @@ func (a *Allocation) uploadOrUpdateFile(localpath string, hash.Write(file) contentHash := hex.EncodeToString(hash.Sum(nil)) if contentHash != fileRef.ActualFileHash { - return errors.New("Content hash doesn't match") + return errors.New("", "Content hash doesn't match") } uploadReq.filemeta.Hash = fileRef.ActualFileHash @@ -432,7 +431,7 @@ func (a *Allocation) uploadOrUpdateFile(localpath string, } if !uploadReq.IsFullConsensusSupported() { - return errors.New(fmt.Sprintf("allocation requires [%v] blobbers, which is greater than the maximum permitted number of [%v]. reduce number of data or parity shards and try again", uploadReq.fullconsensus, uploadReq.GetMaxBlobbersSupported())) + return fmt.Errorf("allocation requires [%v] blobbers, which is greater than the maximum permitted number of [%v]. reduce number of data or parity shards and try again", uploadReq.fullconsensus, uploadReq.GetMaxBlobbersSupported()) } go func() { @@ -459,7 +458,7 @@ func (a *Allocation) RepairRequired(remotepath string) (zboxutil.Uint128, bool, listReq.remotefilepath = remotepath found, fileRef, _ := listReq.getFileConsensusFromBlobbers() if fileRef == nil { - return found, false, fileRef, errors.New("File not found for the given remotepath") + return found, false, fileRef, errors.New("", "File not found for the given remotepath") } uploadMask := zboxutil.NewUint128(1).Lsh(uint64(len(a.Blobbers))).Sub64(1) @@ -487,13 +486,13 @@ func (a *Allocation) downloadFile(localPath string, remotePath string, contentMo } if stat, err := os.Stat(localPath); err == nil { if !stat.IsDir() { - return errors.New(fmt.Sprintf("Local path is not a directory '%s'", localPath)) + return fmt.Errorf("Local path is not a directory '%s'", localPath) } localPath = strings.TrimRight(localPath, "/") _, rFile := filepath.Split(remotePath) localPath = fmt.Sprintf("%s/%s", localPath, rFile) if _, err := os.Stat(localPath); err == nil { - return errors.New(fmt.Sprintf("Local file already exists '%s'", localPath)) + return fmt.Errorf("Local file already exists '%s'", localPath) } } lPath, _ := filepath.Split(localPath) @@ -907,11 +906,11 @@ func (a *Allocation) RevokeShare(path string, refereeClientID string) error { wg.Wait() if len(success) == len(a.Blobbers) { if len(notFound) == len(a.Blobbers) { - return errors.New("share not found") + return errors.New("", "share not found") } return nil } - return errors.New("consensus not reached") + return errors.New("", "consensus not reached") } func (a *Allocation) GetAuthTicket( @@ -1036,7 +1035,7 @@ func (a *Allocation) UploadAuthTicketToBlobber(authticketB64 string, clientEncPu fullconsensus: float32(a.DataShards + a.ParityShards), } if !consensus.isConsensusOk() { - return errors.New("consensus not reached") + return errors.New("", "consensus not reached") } return nil } @@ -1104,13 +1103,13 @@ func (a *Allocation) downloadFromAuthTicket(localPath string, authTicket string, } if stat, err := os.Stat(localPath); err == nil { if !stat.IsDir() { - return errors.New(fmt.Sprintf("Local path is not a directory '%s'", localPath)) + return fmt.Errorf("Local path is not a directory '%s'", localPath) } localPath = strings.TrimRight(localPath, "/") _, rFile := filepath.Split(remoteFilename) localPath = fmt.Sprintf("%s/%s", localPath, rFile) if _, err := os.Stat(localPath); err == nil { - return errors.New(fmt.Sprintf("Local file already exists '%s'", localPath)) + return fmt.Errorf("Local file already exists '%s'", localPath) } } if len(a.Blobbers) <= 1 { @@ -1264,6 +1263,7 @@ func (a *Allocation) CommitFolderChange(operation, preValue, currValue string) ( time.Sleep(querySleepTime) retries := 0 var t *transaction.Transaction + for retries < blockchain.GetMaxTxnQuery() { t, err = transaction.VerifyTransaction(txn.Hash, blockchain.GetSharders()) if err == nil { diff --git a/zboxcore/sdk/allocation_test.go b/zboxcore/sdk/allocation_test.go index ee0ddeb13..217c99cd0 100644 --- a/zboxcore/sdk/allocation_test.go +++ b/zboxcore/sdk/allocation_test.go @@ -12,11 +12,15 @@ import ( "strings" "sync" "testing" + "time" "github.com/0chain/gosdk/zboxcore/encryption" "github.com/0chain/errors" "github.com/0chain/gosdk/core/common" + "github.com/0chain/gosdk/core/conf" + "github.com/0chain/gosdk/core/resty" + "github.com/0chain/gosdk/core/transaction" "github.com/0chain/gosdk/core/util" "github.com/0chain/gosdk/core/zcncrypto" @@ -2671,6 +2675,13 @@ func TestAllocation_CommitFolderChange(t *testing.T) { var mockClient = mocks.HttpClient{} util.Client = &mockClient + resty.CreateClient = func(t *http.Transport, timeout time.Duration) resty.Client { + return &mockClient + } + + transaction.SetConfig(&conf.Config{ + MinConfirmation: 50, + }) client := zclient.GetClient() client.Wallet = &zcncrypto.Wallet{ @@ -2681,7 +2692,7 @@ func TestAllocation_CommitFolderChange(t *testing.T) { setupHttpResponse := func(t *testing.T, name string, httpMethod string, statusCode int, body []byte) { mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { return req.Method == httpMethod && - strings.HasPrefix(req.URL.Path, name) + strings.Index(req.URL.String(), name) > -1 })).Return(&http.Response{ StatusCode: statusCode, Body: ioutil.NopCloser(bytes.NewReader([]byte(body))), @@ -2694,11 +2705,12 @@ func TestAllocation_CommitFolderChange(t *testing.T) { blockchain.SetQuerySleepTime(1) tests := []struct { - name string - parameters parameters - setup func(*testing.T, string, *Allocation) (teardown func(*testing.T)) - wantErr bool - errMsg string + name string + parameters parameters + setup func(*testing.T, string, *Allocation) (teardown func(*testing.T)) + wantErr bool + errMsg string + exceptedError error }{ { name: "Test_Uninitialized_Failed", @@ -2715,6 +2727,7 @@ func TestAllocation_CommitFolderChange(t *testing.T) { { name: "Test_Sharder_Verify_Txn_Failed", setup: func(t *testing.T, testCaseName string, a *Allocation) (teardown func(t *testing.T)) { + body, err := json.Marshal(&transaction.Transaction{ Hash: mockHash, }) @@ -2723,8 +2736,9 @@ func TestAllocation_CommitFolderChange(t *testing.T) { setupHttpResponse(t, testCaseName+"mockSharders", http.MethodGet, http.StatusBadRequest, []byte("")) return nil }, - wantErr: true, - errMsg: "transaction_not_found: Transaction was not found on any of the sharders", + wantErr: true, + exceptedError: transaction.ErrTooLessConfirmation, + errMsg: "transaction_not_found: Transaction was not found on any of the sharders", }, { name: "Test_Max_Retried_Failed", @@ -2773,17 +2787,25 @@ func TestAllocation_CommitFolderChange(t *testing.T) { } a.InitAllocation() sdkInitialized = true - blockchain.SetMiners([]string{tt.name + "mockMiners"}) - blockchain.SetSharders([]string{tt.name + "mockSharders"}) + blockchain.SetMiners([]string{"http://" + tt.name + "mockMiners"}) + blockchain.SetSharders([]string{"http://" + tt.name + "mockSharders"}) if tt.setup != nil { if teardown := tt.setup(t, tt.name, a); teardown != nil { defer teardown(t) } } + _, err := a.CommitFolderChange(tt.parameters.operation, tt.parameters.preValue, tt.parameters.currValue) require.EqualValues(tt.wantErr, err != nil) if err != nil { - require.EqualValues(tt.errMsg, errors.Top(err)) + + // test it by predefined error variable instead of error message + if tt.exceptedError != nil { + require.ErrorIs(err, tt.exceptedError) + } else { + require.EqualValues(tt.errMsg, errors.Top(err)) + } + return } require.NoErrorf(err, "unexpected error: %v", err) diff --git a/zboxcore/sdk/attributesworker.go b/zboxcore/sdk/attributesworker.go index e2d97072d..2f90676ad 100644 --- a/zboxcore/sdk/attributesworker.go +++ b/zboxcore/sdk/attributesworker.go @@ -10,7 +10,8 @@ import ( "sync" "time" - "github.com/0chain/errors" + "errors" + "github.com/0chain/gosdk/zboxcore/fileref" "github.com/0chain/gosdk/zboxcore/allocationchange" diff --git a/zboxcore/sdk/blockdownloadworker.go b/zboxcore/sdk/blockdownloadworker.go index 680c24e24..4704eadd1 100644 --- a/zboxcore/sdk/blockdownloadworker.go +++ b/zboxcore/sdk/blockdownloadworker.go @@ -129,7 +129,7 @@ func (req *BlockDownloadRequest) downloadBlobberBlock() { if req.blobber.IsSkip() { req.result <- &downloadBlock{Success: false, idx: req.blobberIdx, - err: errors.New("skip blobber by previous errors")} + err: errors.New("", "skip blobber by previous errors")} return } @@ -236,7 +236,7 @@ func (req *BlockDownloadRequest) downloadBlobberBlock() { Logger.Info("Will be retrying download") setBlobberReadCtr(req.blobber, rspData.LatestRM.ReadCounter) shouldRetry = true - return errors.New("Need to retry the download") + return errors.New("", "Need to retry the download") } } else { @@ -244,7 +244,7 @@ func (req *BlockDownloadRequest) downloadBlobberBlock() { if err != nil { return err } - err = errors.New(fmt.Sprintf("Response Error: %s", string(resp_body))) + err = fmt.Errorf("Response Error: %s", string(resp_body)) if strings.Contains(err.Error(), "not_enough_tokens") { shouldRetry, retry = false, 3 // don't repeat req.blobber.SetSkip(true) diff --git a/zboxcore/sdk/copyworker.go b/zboxcore/sdk/copyworker.go index de4ded866..0f8a0f21d 100644 --- a/zboxcore/sdk/copyworker.go +++ b/zboxcore/sdk/copyworker.go @@ -10,7 +10,8 @@ import ( "sync" "time" - "github.com/0chain/errors" + "errors" + "github.com/0chain/gosdk/zboxcore/fileref" "github.com/0chain/gosdk/zboxcore/allocationchange" diff --git a/zboxcore/sdk/deleteworker.go b/zboxcore/sdk/deleteworker.go index ac0e20851..bf44b6f5d 100644 --- a/zboxcore/sdk/deleteworker.go +++ b/zboxcore/sdk/deleteworker.go @@ -13,7 +13,8 @@ import ( "sync" "time" - "github.com/0chain/errors" + "errors" + "github.com/0chain/gosdk/zboxcore/allocationchange" "github.com/0chain/gosdk/zboxcore/blockchain" "github.com/0chain/gosdk/zboxcore/fileref" @@ -120,7 +121,7 @@ func (req *DeleteRequest) ProcessDelete() error { req.wg.Wait() if !req.isConsensusOk() { - return errors.New(fmt.Sprintf("Delete failed: Success_rate:%2f, expected:%2f", req.getConsensusRate(), req.getConsensusRequiredForOk())) + return fmt.Errorf("Delete failed: Success_rate:%2f, expected:%2f", req.getConsensusRate(), req.getConsensusRequiredForOk()) } req.consensus = 0 diff --git a/zboxcore/sdk/downloadworker.go b/zboxcore/sdk/downloadworker.go index 6b1c310ab..173c9fc40 100644 --- a/zboxcore/sdk/downloadworker.go +++ b/zboxcore/sdk/downloadworker.go @@ -194,7 +194,7 @@ func (req *DownloadRequest) processDownload(ctx context.Context) { req.downloadMask, fileRef, _ = listReq.getFileConsensusFromBlobbers() if req.downloadMask.Equals64(0) || fileRef == nil { if req.statusCallback != nil { - req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("No minimum consensus for file meta data of file")) + req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("", "No minimum consensus for file meta data of file")) } return } @@ -265,7 +265,7 @@ func (req *DownloadRequest) processDownload(ctx context.Context) { req.isDownloadCanceled = false os.Remove(req.localpath) if req.statusCallback != nil { - req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("Download aborted by user")) + req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("","Download aborted by user")) } return } @@ -303,7 +303,7 @@ func (req *DownloadRequest) processDownload(ctx context.Context) { if calcHash != expectedHash { os.Remove(req.localpath) if req.statusCallback != nil { - req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("File content didn't match with uploaded file")) + req.statusCallback.Error(req.allocationID, remotePathCallback, OpDownload, errors.New("","File content didn't match with uploaded file")) } return } diff --git a/zboxcore/sdk/filemetaworker_test.go b/zboxcore/sdk/filemetaworker_test.go index f36650efd..a63894576 100644 --- a/zboxcore/sdk/filemetaworker_test.go +++ b/zboxcore/sdk/filemetaworker_test.go @@ -15,6 +15,7 @@ import ( "testing" "github.com/0chain/errors" + "github.com/0chain/gosdk/core/zcncrypto" "github.com/0chain/gosdk/zboxcore/blockchain" zclient "github.com/0chain/gosdk/zboxcore/client" @@ -70,7 +71,7 @@ func TestListRequest_getFileMetaInfoFromBlobber(t *testing.T) { })).Return(&http.Response{ Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), StatusCode: p.respStatusCode, - }, errors.New(mockErrorMessage)) + }, errors.New("", mockErrorMessage)) }, wantErr: true, errMsg: mockErrorMessage, diff --git a/zboxcore/sdk/filestatsworker_test.go b/zboxcore/sdk/filestatsworker_test.go index d6c8a8703..fbc1f7c29 100644 --- a/zboxcore/sdk/filestatsworker_test.go +++ b/zboxcore/sdk/filestatsworker_test.go @@ -74,7 +74,7 @@ func TestListRequest_getFileStatsInfoFromBlobber(t *testing.T) { })).Return(&http.Response{ Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), StatusCode: p.respStatusCode, - }, errors.New(mockErrorMessage)) + }, errors.New("", mockErrorMessage)) }, wantErr: true, errMsg: mockErrorMessage, diff --git a/zboxcore/sdk/listworker.go b/zboxcore/sdk/listworker.go index 8c977fb6b..5fab52792 100644 --- a/zboxcore/sdk/listworker.go +++ b/zboxcore/sdk/listworker.go @@ -120,9 +120,10 @@ func (req *ListRequest) getListInfoFromBlobber(blobber *blockchain.StorageNode, return errors.Wrap(err, "error getting the dir tree from list response:") } return nil - } else { - return errors.New(fmt.Sprintf("error from server list response: %s", s.String())) } + + return fmt.Errorf("error from server list response: %s", s.String()) + }) } diff --git a/zboxcore/sdk/listworker_test.go b/zboxcore/sdk/listworker_test.go index ad288ff11..173fc107d 100644 --- a/zboxcore/sdk/listworker_test.go +++ b/zboxcore/sdk/listworker_test.go @@ -80,7 +80,7 @@ func TestListRequest_getListInfoFromBlobber(t *testing.T) { })).Return(&http.Response{ Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), StatusCode: p.respStatusCode, - }, errors.New(mockErrorMessage)) + }, errors.New("", mockErrorMessage)) }, wantErr: true, errMsg: mockErrorMessage, diff --git a/zboxcore/sdk/renameworker.go b/zboxcore/sdk/renameworker.go index 8459935c7..282639421 100644 --- a/zboxcore/sdk/renameworker.go +++ b/zboxcore/sdk/renameworker.go @@ -10,7 +10,8 @@ import ( "sync" "time" - "github.com/0chain/errors" + "errors" + "github.com/0chain/gosdk/zboxcore/fileref" "github.com/0chain/gosdk/zboxcore/allocationchange" diff --git a/zboxcore/sdk/sdk.go b/zboxcore/sdk/sdk.go index 4c07f8516..2cc5fac0c 100644 --- a/zboxcore/sdk/sdk.go +++ b/zboxcore/sdk/sdk.go @@ -13,8 +13,6 @@ import ( "github.com/0chain/errors" "github.com/0chain/gosdk/core/logger" - "github.com/0chain/gosdk/zboxcore/marker" - "github.com/0chain/gosdk/core/common" "github.com/0chain/gosdk/core/transaction" "github.com/0chain/gosdk/core/version" @@ -22,6 +20,7 @@ import ( "github.com/0chain/gosdk/zboxcore/client" "github.com/0chain/gosdk/zboxcore/encryption" . "github.com/0chain/gosdk/zboxcore/logger" + "github.com/0chain/gosdk/zboxcore/marker" "github.com/0chain/gosdk/zboxcore/zboxutil" ) @@ -192,7 +191,7 @@ func GetReadPoolInfo(clientID string) (info *AllocationPoolStats, err error) { return nil, errors.Wrap(err, "error requesting read pool info") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } info = new(AllocationPoolStats) @@ -336,7 +335,7 @@ func GetStakePoolInfo(blobberID string) (info *StakePoolInfo, err error) { return nil, errors.Wrap(err, "error requesting stake pool info:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } info = new(StakePoolInfo) @@ -369,7 +368,7 @@ func GetStakePoolUserInfo(clientID string) (info *StakePoolUserInfo, err error) return nil, errors.Wrap(err, "error requesting stake pool user info:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } info = new(StakePoolUserInfo) @@ -491,7 +490,7 @@ func GetWritePoolInfo(clientID string) (info *AllocationPoolStats, err error) { return nil, errors.Wrap(err, "error requesting read pool info:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } info = new(AllocationPoolStats) @@ -576,7 +575,7 @@ func GetChallengePoolInfo(allocID string) (info *ChallengePoolInfo, err error) { return nil, errors.Wrap(err, "error requesting challenge pool info:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } info = new(ChallengePoolInfo) @@ -601,7 +600,7 @@ func GetMptData(key string) ([]byte, error) { return nil, errors.Wrap(err, "error requesting mpt key data:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } return b, nil @@ -668,7 +667,7 @@ func GetStorageSCConfig() (conf *StorageSCConfig, err error) { return nil, errors.Wrap(err, "error requesting storage SC configs:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("", "empty response") } conf = new(StorageSCConfig) @@ -677,7 +676,7 @@ func GetStorageSCConfig() (conf *StorageSCConfig, err error) { } if conf.ReadPool == nil || conf.WritePool == nil || conf.StakePool == nil { - return nil, errors.New("invalid confg: missing read/write/stake pool configs") + return nil, errors.New("", "invalid confg: missing read/write/stake pool configs") } return } @@ -705,7 +704,7 @@ func GetBlobbers() (bs []*Blobber, err error) { return nil, errors.Wrap(err, "error requesting blobbers:") } if len(b) == 0 { - return nil, errors.New("empty response") + return nil, errors.New("","empty response") } type nodes struct { @@ -736,7 +735,7 @@ func GetBlobber(blobberID string) (blob *Blobber, err error) { return nil, errors.Wrap(err, "requesting blobber:") } if len(b) == 0 { - return nil, errors.New("empty response from sharders") + return nil, errors.New("","empty response from sharders") } blob = new(Blobber) if err = json.Unmarshal(b, blob); err != nil { diff --git a/zboxcore/sdk/sync.go b/zboxcore/sdk/sync.go index 01c2f5829..05633c9f5 100644 --- a/zboxcore/sdk/sync.go +++ b/zboxcore/sdk/sync.go @@ -279,11 +279,11 @@ func (a *Allocation) GetAllocationDiff(lastSyncCachePath string, localRootPath s } content, err := ioutil.ReadFile(lastSyncCachePath) if err != nil { - return lFdiff, errors.New("can't read cache file.") + return lFdiff, errors.New("", "can't read cache file.") } err = json.Unmarshal(content, &prevRemoteFileMap) if err != nil { - return lFdiff, errors.New("invalid cache content.") + return lFdiff, errors.New("", "invalid cache content.") } } } diff --git a/zboxcore/sdk/uploadworker.go b/zboxcore/sdk/uploadworker.go index b9f3b362d..1f86ae3f0 100644 --- a/zboxcore/sdk/uploadworker.go +++ b/zboxcore/sdk/uploadworker.go @@ -288,7 +288,7 @@ func (req *UploadRequest) prepareUpload( } if resp.StatusCode != http.StatusOK { Logger.Error(blobber.Baseurl, " Upload error response: ", resp.StatusCode, string(respbody)) - req.err = errors.New(string(respbody)) + req.err = errors.New("", string(respbody)) return err } var r uploadResult @@ -300,7 +300,7 @@ func (req *UploadRequest) prepareUpload( } if r.Filename != formData.Filename || r.ShardSize != shardSize || r.Hash != formData.Hash || r.MerkleRoot != formData.MerkleRoot { - err = errors.New(fmt.Sprintf(blobber.Baseurl, "Unexpected upload response data", string(respbody))) + err = fmt.Errorf(blobber.Baseurl, "Unexpected upload response data", string(respbody)) Logger.Error(err) req.err = err return err @@ -428,7 +428,7 @@ func (req *UploadRequest) completePush() error { } req.wg.Wait() if !req.isConsensusOk() { - return errors.New(fmt.Sprintf("Upload failed: Consensus_rate:%f, expected:%f", req.getConsensusRate(), req.getConsensusRequiredForOk())) + return fmt.Errorf("Upload failed: Consensus_rate:%f, expected:%f", req.getConsensusRate(), req.getConsensusRequiredForOk()) } return nil } diff --git a/zboxcore/zboxutil/util.go b/zboxcore/zboxutil/util.go index e14ccd42c..fa008686f 100644 --- a/zboxcore/zboxutil/util.go +++ b/zboxcore/zboxutil/util.go @@ -12,7 +12,8 @@ import ( "path/filepath" "strings" - "github.com/0chain/errors" + "errors" + "github.com/0chain/gosdk/core/util" "github.com/0chain/gosdk/zboxcore/blockchain" "github.com/h2non/filetype" diff --git a/zcncore/mswallet.go b/zcncore/mswallet.go index dee2e8173..c8d0cfd89 100644 --- a/zcncore/mswallet.go +++ b/zcncore/mswallet.go @@ -56,7 +56,7 @@ type MSTransfer struct { func (msw *MSWallet) Marshal() (string, error) { msws, err := json.Marshal(msw) if err != nil { - return "", errors.New("Invalid Wallet") + return "", errors.New("", "Invalid Wallet") } return string(msws), nil } @@ -70,7 +70,7 @@ type MSVoteCallback interface { func CreateMSWallet(t, n int) (string, string, []string, error) { id := 0 if _config.chain.SignatureScheme != "bls0chain" { - return "", "", nil, errors.New("encryption scheme for this blockchain is not bls0chain") + return "", "", nil, errors.New("", "encryption scheme for this blockchain is not bls0chain") } @@ -141,11 +141,11 @@ func RegisterWallet(walletString string, cb WalletCallback) { func CreateMSVote(proposal, grpClientID, signerWalletstr, toClientID string, token int64) (string, error) { if proposal == "" || grpClientID == "" || toClientID == "" || signerWalletstr == "" { - return "", errors.New("proposal or groupClient or signer wallet or toClientID cannot be empty") + return "", errors.New("", "proposal or groupClient or signer wallet or toClientID cannot be empty") } if token < 1 { - return "", errors.New("Token cannot be less than 1") + return "", errors.New("", "Token cannot be less than 1") } signerWallet, err := GetWallet(signerWalletstr) diff --git a/zcncore/transaction.go b/zcncore/transaction.go index 7c0d01b10..d244604bb 100644 --- a/zcncore/transaction.go +++ b/zcncore/transaction.go @@ -10,6 +10,7 @@ import ( "github.com/0chain/errors" "github.com/0chain/gosdk/core/block" "github.com/0chain/gosdk/core/common" + "github.com/0chain/gosdk/core/encryption" "github.com/0chain/gosdk/core/transaction" "github.com/0chain/gosdk/core/util" @@ -25,11 +26,11 @@ var ( ) var ( - errNetwork = errors.New("network error. host not reachable") - errUserRejected = errors.New("rejected by user") - errAuthVerifyFailed = errors.New("verfication failed for auth response") - errAuthTimeout = errors.New("auth timed out") - errAddSignature = errors.New("error adding signature") + errNetwork = errors.New("", "network error. host not reachable") + errUserRejected = errors.New("", "rejected by user") + errAuthVerifyFailed = errors.New("", "verfication failed for auth response") + errAuthTimeout = errors.New("", "auth timed out") + errAddSignature = errors.New("", "error adding signature") ) // TransactionCallback needs to be implemented by the caller for transaction related APIs @@ -163,7 +164,7 @@ func signWithWallet(hash string, wi interface{}) (string, error) { if !ok { fmt.Printf("Error in casting to wallet") - return "", errors.New("error in casting to wallet") + return "", errors.New("", "error in casting to wallet") } sigScheme := zcncrypto.NewSignatureScheme(_config.chain.SignatureScheme) sigScheme.SetPrivateKey(w.Keys[0].PrivateKey) @@ -260,7 +261,7 @@ func (t *Transaction) submitTxn() { } rate := consensus * 100 / float32(len(randomMiners)) if rate < consensusThresh { - t.completeTxn(StatusError, "", errors.New(fmt.Sprintf("submit transaction failed. %s", tFailureRsp))) + t.completeTxn(StatusError, "", fmt.Errorf("submit transaction failed. %s", tFailureRsp)) return } time.Sleep(3 * time.Second) @@ -284,7 +285,7 @@ func NewTransaction(cb TransactionCallback, txnFee int64) (TransactionScheme, er } if _config.isSplitWallet { if _config.authUrl == "" { - return nil, errors.New("auth url not set") + return nil, errors.New("", "auth url not set") } Logger.Info("New transaction interface with auth") return newTransactionWithAuth(cb, txnFee) @@ -295,7 +296,7 @@ func NewTransaction(cb TransactionCallback, txnFee int64) (TransactionScheme, er func (t *Transaction) SetTransactionCallback(cb TransactionCallback) error { if t.txnStatus != StatusUnknown { - return errors.New("transaction already exists. cannot set transaction hash.") + return errors.New("", "transaction already exists. cannot set transaction hash.") } t.txnCb = cb return nil @@ -303,7 +304,7 @@ func (t *Transaction) SetTransactionCallback(cb TransactionCallback) error { func (t *Transaction) SetTransactionFee(txnFee int64) error { if t.txnStatus != StatusUnknown { - return errors.New("transaction already exists. cannot set transaction fee.") + return errors.New("", "transaction already exists. cannot set transaction fee.") } t.txn.TransactionFee = txnFee return nil @@ -398,7 +399,7 @@ func (t *Transaction) ExecuteSmartContract(address, methodName, jsoninput string func (t *Transaction) SetTransactionHash(hash string) error { if t.txnStatus != StatusUnknown { - return errors.New("transaction already exists. cannot set transaction hash.") + return errors.New("", "transaction already exists. cannot set transaction hash.") } t.txnHash = hash return nil @@ -465,17 +466,17 @@ func getBlockHeaderFromTransactionConfirmation(txnHash string, cfmBlock map[stri return nil, errors.Wrap(err, "txn confirmation parse error.") } if cfm.Transaction == nil { - return nil, errors.New(fmt.Sprintf("missing transaction %s in block confirmation", txnHash)) + return nil, fmt.Errorf("missing transaction %s in block confirmation", txnHash) } if txnHash != cfm.Transaction.Hash { - return nil, errors.New(fmt.Sprintf("invalid transaction hash. Expected: %s. Received: %s", txnHash, cfm.Transaction.Hash)) + return nil, fmt.Errorf("invalid transaction hash. Expected: %s. Received: %s", txnHash, cfm.Transaction.Hash) } if !util.VerifyMerklePath(cfm.Transaction.Hash, cfm.MerkleTreePath, cfm.MerkleTreeRoot) { - return nil, errors.New("txn merkle validation failed.") + return nil, errors.New("", "txn merkle validation failed.") } txnRcpt := transaction.NewTransactionReceipt(cfm.Transaction) if !util.VerifyMerklePath(txnRcpt.GetHash(), cfm.ReceiptMerkleTreePath, cfm.ReceiptMerkleTreeRoot) { - return nil, errors.New("txn receipt cmerkle validation failed.") + return nil, errors.New("", "txn receipt cmerkle validation failed.") } prevBlockHash := cfm.PreviousBlockHash block.MinerId = cfm.MinerID @@ -489,10 +490,10 @@ func getBlockHeaderFromTransactionConfirmation(txnHash string, cfmBlock map[stri if isBlockExtends(prevBlockHash, block) { return block, nil } else { - return nil, errors.New("block hash verification failed in confirmation") + return nil, errors.New("", "block hash verification failed in confirmation") } } - return nil, errors.New("txn confirmation not found.") + return nil, errors.New("", "txn confirmation not found.") } func getTransactionConfirmation(numSharders int, txnHash string) (*blockHeader, map[string]json.RawMessage, *blockHeader, error) { @@ -540,7 +541,7 @@ func getTransactionConfirmation(numSharders int, txnHash string) (*blockHeader, } } if maxConfirmation == 0 { - return nil, confirmation, &lfb, errors.New("transaction not found") + return nil, confirmation, &lfb, errors.New("", "transaction not found") } return blockHdr, confirmation, &lfb, nil } @@ -580,7 +581,7 @@ func GetLatestFinalized(ctx context.Context, numSharders int) (b *block.Header, } if maxConsensus == 0 { - return nil, errors.New("block info not found") + return nil, errors.New("", "block info not found") } return @@ -627,7 +628,7 @@ func GetLatestFinalizedMagicBlock(ctx context.Context, numSharders int) (m *bloc } if maxConsensus == 0 { - return nil, errors.New("magic block info not found") + return nil, errors.New("", "magic block info not found") } return @@ -720,7 +721,7 @@ func GetBlockByRound(ctx context.Context, numSharders int, round int64) (b *bloc } if maxConsensus == 0 { - return nil, errors.New("round info not found") + return nil, errors.New("", "round info not found") } return @@ -770,7 +771,7 @@ func GetMagicBlockByNumber(ctx context.Context, numSharders int, number int64) ( } if maxConsensus == 0 { - return nil, errors.New("magic block info not found") + return nil, errors.New("", "magic block info not found") } return @@ -822,7 +823,7 @@ func getBlockInfoByRound(numSharders int, round int64, content string) (*blockHe } } if maxConsensus == 0 { - return nil, errors.New("round info not found.") + return nil, errors.New("", "round info not found.") } return &blkHdr, nil } @@ -883,12 +884,12 @@ func (t *Transaction) isTransactionExpired(lfbCreationTime, currentTime int64) b } func (t *Transaction) Verify() error { if t.txnHash == "" && t.txnStatus == StatusUnknown { - return errors.New("invalid transaction. cannot be verified.") + return errors.New("", "invalid transaction. cannot be verified.") } if t.txnHash == "" && t.txnStatus == StatusSuccess { h := t.GetTransactionHash() if h == "" { - return errors.New("invalid transaction. cannot be verified.") + return errors.New("", "invalid transaction. cannot be verified.") } } // If transaction is verify only start from current time @@ -908,14 +909,14 @@ func (t *Transaction) Verify() error { confirmBlock, confirmation, lfb, err = getTransactionConfirmation(getMinShardersVerify(), t.txnHash) if err != nil { if t.isTransactionExpired(lfb.CreationDate, tn) { - t.completeVerify(StatusError, "", errors.New(`{"error": "verify transaction failed"}`)) + t.completeVerify(StatusError, "", errors.New("", `{"error": "verify transaction failed"}`)) return } continue } } else { if t.isTransactionExpired(lfb.CreationDate, tn) { - t.completeVerify(StatusError, "", errors.New(`{"error": "verify transaction failed"}`)) + t.completeVerify(StatusError, "", errors.New("", `{"error": "verify transaction failed"}`)) return } continue @@ -925,7 +926,7 @@ func (t *Transaction) Verify() error { if valid { output, err := json.Marshal(confirmation) if err != nil { - t.completeVerify(StatusError, "", errors.New(`{"error": "transaction confirmation json marshal error"`)) + t.completeVerify(StatusError, "", errors.New("", `{"error": "transaction confirmation json marshal error"`)) return } t.completeVerify(StatusSuccess, string(output), nil) diff --git a/zcncore/transactionauth.go b/zcncore/transactionauth.go index 3d67c170f..374798891 100644 --- a/zcncore/transactionauth.go +++ b/zcncore/transactionauth.go @@ -93,7 +93,7 @@ func verifyFn(signature, msgHash, publicKey string) (bool, error) { v.SetPublicKey(publicKey) ok, err := v.Verify(signature, msgHash) if err != nil || ok == false { - return false, errors.New(`{"error": "signature_mismatch"}`) + return false, errors.New("", `{"error": "signature_mismatch"}`) } return true, nil } @@ -353,7 +353,7 @@ func (ta *TransactionWithAuth) UnlockTokens(poolID string) error { //RegisterMultiSig register a multisig wallet with the SC. func (ta *TransactionWithAuth) RegisterMultiSig(walletstr string, mswallet string) error { - return errors.New("not implemented") + return errors.New("", "not implemented") } // diff --git a/zcncore/wallet.go b/zcncore/wallet.go index 96c3aa86b..f726d2f52 100644 --- a/zcncore/wallet.go +++ b/zcncore/wallet.go @@ -200,14 +200,14 @@ func init() { } func checkSdkInit() error { if !_config.isConfigured || len(_config.chain.Miners) < 1 || len(_config.chain.Sharders) < 1 { - return errors.New("SDK not initialized") + return errors.New("", "SDK not initialized") } return nil } func checkWalletConfig() error { if !_config.isValidWallet || _config.wallet.ClientID == "" { Logger.Error("wallet info not found. returning error.") - return errors.New("wallet info not found. set wallet info") + return errors.New("", "wallet info not found. set wallet info") } return nil } @@ -294,7 +294,7 @@ func Init(c string) error { if err == nil { // Check signature scheme is supported if _config.chain.SignatureScheme != "ed25519" && _config.chain.SignatureScheme != "bls0chain" { - return errors.New("invalid/unsupported signature scheme") + return errors.New("", "invalid/unsupported signature scheme") } err := UpdateNetworkDetails() @@ -342,7 +342,7 @@ func WithConfirmationChainLength(m int) func(c *ChainConfig) error { // InitZCNSDK initializes the SDK with miner, sharder and signature scheme provided. func InitZCNSDK(blockWorker string, signscheme string, configs ...func(*ChainConfig) error) error { if signscheme != "ed25519" && signscheme != "bls0chain" { - return errors.New("invalid/unsupported signature scheme") + return errors.New("", "invalid/unsupported signature scheme") } _config.chain.BlockWorker = blockWorker _config.chain.SignatureScheme = signscheme @@ -388,7 +388,7 @@ func GetNetworkJSON() string { // It also registers the wallet again to block chain. func CreateWallet(statusCb WalletCallback) error { if len(_config.chain.Miners) < 1 || len(_config.chain.Sharders) < 1 { - return errors.New("SDK not initialized") + return errors.New("", "SDK not initialized") } go func() { sigScheme := zcncrypto.NewSignatureScheme(_config.chain.SignatureScheme) @@ -410,7 +410,7 @@ func CreateWallet(statusCb WalletCallback) error { // It also registers the wallet again to block chain. func RecoverWallet(mnemonic string, statusCb WalletCallback) error { if zcncrypto.IsMnemonicValid(mnemonic) != true { - return errors.New("Invalid mnemonic") + return errors.New("", "Invalid mnemonic") } go func() { sigScheme := zcncrypto.NewSignatureScheme(_config.chain.SignatureScheme) @@ -431,7 +431,7 @@ func RecoverWallet(mnemonic string, statusCb WalletCallback) error { // Split keys from the primary master key func SplitKeys(privateKey string, numSplits int) (string, error) { if _config.chain.SignatureScheme != "bls0chain" { - return "", errors.New("signature key doesn't support split key") + return "", errors.New("", "signature key doesn't support split key") } sigScheme := zcncrypto.NewBLS0ChainScheme() err := sigScheme.SetPrivateKey(privateKey) @@ -489,7 +489,7 @@ func RegisterToMiners(wallet *zcncrypto.Wallet, statusCb WalletCallback) error { } rate := consensus * 100 / float32(len(_config.chain.Miners)) if rate < consensusThresh { - return errors.New(fmt.Sprintf("Register consensus not met. Consensus: %f, Expected: %f", rate, consensusThresh)) + return fmt.Errorf("Register consensus not met. Consensus: %f, Expected: %f", rate, consensusThresh) } w, err := wallet.Marshal() if err != nil { @@ -551,10 +551,10 @@ func SetWalletInfo(w string, splitKeyWallet bool) error { // SetAuthUrl will be called by app to set zauth URL to SDK. func SetAuthUrl(url string) error { if !_config.isSplitWallet { - return errors.New("wallet type is not split key") + return errors.New("", "wallet type is not split key") } if url == "" { - return errors.New("invalid auth url") + return errors.New("", "invalid auth url") } _config.authUrl = strings.TrimRight(url, "/") return nil @@ -641,7 +641,7 @@ func getBalanceFromSharders(clientID string) (int64, string, error) { } rate := consensus * 100 / float32(len(_config.chain.Sharders)) if rate < consensusThresh { - return 0, winError, errors.New("get balance failed. consensus not reached") + return 0, winError, errors.New("", "get balance failed. consensus not reached") } return winBalance, winInfo, nil }