diff --git a/README.md b/README.md index c87d7fe4..65a79dc0 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,14 @@ For more details, go to [docs.elrond.com](https://docs.elrond.com/tools/proxy). - `/address/:address` (GET) --> returns the account's data in JSON format for the given :address. - `/address/:address/balance` (GET) --> returns the balance of a given :address. - `/address/:address/nonce` (GET) --> returns the nonce of an :address. +- `/address/:address/shard` (GET) --> returns the shard of an :address based on current proxy's configuration. - `/address/:address/storage/:key` (GET) --> returns the value for a given key for an account. - `/address/:address/transactions` (GET) --> returns the transactions stored in indexer for a given :address. ### transaction - `/transaction/send` (POST) --> receives a single transaction in JSON format and forwards it to an observer in the same shard as the sender's shard ID. Returns the transaction's hash if successful or the interceptor error otherwise. +- `/transaction/simulate` (POST) --> same as /transaction/send but does not execute it. will output simulation results - `/transaction/send-multiple` (POST) --> receives a bulk of transactions in JSON format and will forward them to observers in the rights shards. Will return the number of transactions which were accepted by the interceptor and forwarded on the p2p topic. - `/transaction/send-user-funds` (POST) --> receives a request containing `address`, `numOfTxs` and `value` and will select a random account from the PEM file in the same shard as the address received. Will return the transaction's hash if successful or the interceptor error otherwise. - `/transaction/cost` (POST) --> receives a single transaction in JSON format and returns it's cost @@ -38,6 +40,7 @@ For more details, go to [docs.elrond.com](https://docs.elrond.com/tools/proxy). - `/network/status/:shard` (GET) --> returns the status metrics from an observer in the given shard - `/network/config` (GET) --> returns the configuration of the network from any observer +- `/network/economics` (GET) --> returns the economics data metric from the last epoch ### node diff --git a/api/api.go b/api/api.go index e90bf6d2..0f4d2029 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "net/http" "reflect" "github.com/ElrondNetwork/elrond-proxy-go/api/address" @@ -24,18 +25,24 @@ type validatorInput struct { Validator validator.Func } -// Start will boot up the api and appropriate routes, handlers and validators -func Start(elrondProxyFacade ElrondProxyHandler, port int) error { +// CreateServer creates a HTTP server +func CreateServer(elrondProxyFacade ElrondProxyHandler, port int) (*http.Server, error) { ws := gin.Default() ws.Use(cors.Default()) err := registerValidators() if err != nil { - return err + return nil, err } + registerRoutes(ws, elrondProxyFacade) - return ws.Run(fmt.Sprintf(":%d", port)) + httpServer := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: ws, + } + + return httpServer, nil } func registerRoutes(ws *gin.Engine, elrondProxyFacade ElrondProxyHandler) { diff --git a/api/mock/facade.go b/api/mock/facade.go index 13db3bbc..e589ea77 100644 --- a/api/mock/facade.go +++ b/api/mock/facade.go @@ -3,6 +3,7 @@ package mock import ( "math/big" + "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-go/data/vm" "github.com/ElrondNetwork/elrond-proxy-go/data" ) @@ -26,6 +27,7 @@ type Facade struct { GetTransactionStatusHandler func(txHash string, sender string) (string, error) GetConfigMetricsHandler func() (*data.GenericAPIResponse, error) GetNetworkMetricsHandler func(shardID uint32) (*data.GenericAPIResponse, error) + GetEconomicsDataMetricsHandler func() (*data.GenericAPIResponse, error) GetBlockByShardIDAndNonceHandler func(shardID uint32, nonce uint64) (data.AtlasBlock, error) GetTransactionByHashAndSenderAddressHandler func(txHash string, sndAddr string) (*data.FullTransaction, int, error) GetBlockByHashCalled func(shardID uint32, hash string, withTxs bool) (*data.BlockApiResponse, error) @@ -61,6 +63,15 @@ func (f *Facade) GetNetworkConfigMetrics() (*data.GenericAPIResponse, error) { return nil, nil } +// GetEconomicsDataMetrics - +func (f *Facade) GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) { + if f.GetEconomicsDataMetricsHandler != nil { + return f.GetEconomicsDataMetricsHandler() + } + + return &data.GenericAPIResponse{}, nil +} + // ValidatorStatistics - func (f *Facade) ValidatorStatistics() (map[string]*data.ValidatorApiResponse, error) { return f.ValidatorStatisticsHandler() @@ -106,6 +117,11 @@ func (f *Facade) SimulateTransaction(tx *data.Transaction) (*data.ResponseTransa return f.SimulateTransactionHandler(tx) } +// GetAddressConverter - +func (f *Facade) GetAddressConverter() (core.PubkeyConverter, error) { + return nil, nil +} + // SendMultipleTransactions - func (f *Facade) SendMultipleTransactions(txs []*data.Transaction) (data.MultipleTransactionsResponseData, error) { return f.SendMultipleTransactionsHandler(txs) diff --git a/api/network/interface.go b/api/network/interface.go index 270170a9..5af55ab0 100644 --- a/api/network/interface.go +++ b/api/network/interface.go @@ -6,4 +6,5 @@ import "github.com/ElrondNetwork/elrond-proxy-go/data" type FacadeHandler interface { GetNetworkStatusMetrics(shardID uint32) (*data.GenericAPIResponse, error) GetNetworkConfigMetrics() (*data.GenericAPIResponse, error) + GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) } diff --git a/api/network/routes.go b/api/network/routes.go index 4e0382a9..7454ebab 100644 --- a/api/network/routes.go +++ b/api/network/routes.go @@ -13,6 +13,7 @@ import ( func Routes(router *gin.RouterGroup) { router.GET("/status/:shard", GetNetworkStatusData) router.GET("/config", GetNetworkConfigData) + router.GET("/economics", GetEconomicsData) } // GetNetworkStatusData will expose the node network metrics for the given shard @@ -29,7 +30,7 @@ func GetNetworkStatusData(c *gin.Context) { return } - networkStatusResults, err := ef.GetNetworkStatusMetrics(uint32(shardIDUint)) + networkStatusResults, err := ef.GetNetworkStatusMetrics(shardIDUint) if err != nil { shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError) return @@ -54,3 +55,20 @@ func GetNetworkConfigData(c *gin.Context) { c.JSON(http.StatusOK, networkConfigResults) } + +// GetEconomicsData will expose the economics data metrics from an observer (if any available) in json format +func GetEconomicsData(c *gin.Context) { + ef, ok := c.MustGet("elrondProxyFacade").(FacadeHandler) + if !ok { + shared.RespondWithInvalidAppContext(c) + return + } + + economicsData, err := ef.GetEconomicsDataMetrics() + if err != nil { + shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError) + return + } + + c.JSON(http.StatusOK, economicsData) +} diff --git a/api/network/routes_test.go b/api/network/routes_test.go index bf2a8f3b..ff448ce7 100644 --- a/api/network/routes_test.go +++ b/api/network/routes_test.go @@ -209,3 +209,63 @@ func TestGetNetworkConfigData_OkRequestShouldWork(t *testing.T) { assert.True(t, ok) assert.Equal(t, value, res) } + +func TestGetEconomicsData_FailsWithWrongFacadeTypeConversion(t *testing.T) { + t.Parallel() + + ws := startNodeServerWrongFacade() + req, _ := http.NewRequest("GET", "/network/economics", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + ecResp := data.GenericAPIResponse{} + loadResponse(resp.Body, &ecResp) + + assert.Equal(t, resp.Code, http.StatusInternalServerError) + assert.Equal(t, ecResp.Error, apiErrors.ErrInvalidAppContext.Error()) +} + +func TestGetEconomicsData_ShouldErr(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("internal error") + facade := mock.Facade{ + GetEconomicsDataMetricsHandler: func() (*data.GenericAPIResponse, error) { + return nil, expectedErr + }, + } + ws := startNodeServer(&facade) + + req, _ := http.NewRequest("GET", "/network/economics", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + ecDataResp := data.GenericAPIResponse{} + loadResponse(resp.Body, &ecDataResp) + + assert.Equal(t, http.StatusInternalServerError, resp.Code) + assert.Equal(t, expectedErr.Error(), ecDataResp.Error) +} + +func TestGetEconomicsData_ShouldWork(t *testing.T) { + t.Parallel() + + expectedResp := data.GenericAPIResponse{Data: map[string]interface{}{"erd_total_supply": "12345"}} + facade := mock.Facade{ + GetEconomicsDataMetricsHandler: func() (*data.GenericAPIResponse, error) { + return &expectedResp, nil + }, + } + ws := startNodeServer(&facade) + + req, _ := http.NewRequest("GET", "/network/economics", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + ecDataResp := data.GenericAPIResponse{} + loadResponse(resp.Body, &ecDataResp) + + assert.Equal(t, http.StatusOK, resp.Code) + assert.Equal(t, expectedResp, ecDataResp) + assert.Equal(t, expectedResp.Data, ecDataResp.Data) //extra safe +} diff --git a/api/node/routes_test.go b/api/node/routes_test.go index 1d2f7533..0d9b245a 100644 --- a/api/node/routes_test.go +++ b/api/node/routes_test.go @@ -69,7 +69,7 @@ func loadResponse(rsp io.Reader, destination interface{}) { logError(err) } -func TestGetAccount_FailsWithWrongFacadeTypeConversion(t *testing.T) { +func TestHeartbeat_FailsWithWrongFacadeTypeConversion(t *testing.T) { t.Parallel() ws := startNodeServerWrongFacade() diff --git a/cmd/proxy/config/config.toml b/cmd/proxy/config/config.toml index 46428c11..c28a8a90 100644 --- a/cmd/proxy/config/config.toml +++ b/cmd/proxy/config/config.toml @@ -32,6 +32,12 @@ # Type specifies the type of public keys: hex or bech32 Type = "bech32" +[Marshalizer] + Type = "gogo protobuf" + +[Hasher] + Type = "blake2b" + # List of Observers. If you want to define a metachain observer (needed for validator statistics route) use # shard id 4294967295 [[Observers]] diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 49db66de..4868a4f8 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -1,17 +1,20 @@ package main import ( + "context" "fmt" "math/big" + "net/http" "os" "os/signal" - "syscall" "time" logger "github.com/ElrondNetwork/elrond-go-logger" erdConfig "github.com/ElrondNetwork/elrond-go/config" "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-go/data/state/factory" + hasherFactory "github.com/ElrondNetwork/elrond-go/hashing/factory" + marshalFactory "github.com/ElrondNetwork/elrond-go/marshal/factory" "github.com/ElrondNetwork/elrond-go/sharding" "github.com/ElrondNetwork/elrond-proxy-go/api" "github.com/ElrondNetwork/elrond-proxy-go/config" @@ -23,6 +26,7 @@ import ( "github.com/ElrondNetwork/elrond-proxy-go/process/cache" "github.com/ElrondNetwork/elrond-proxy-go/process/database" processFactory "github.com/ElrondNetwork/elrond-proxy-go/process/factory" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta" "github.com/ElrondNetwork/elrond-proxy-go/testing" "github.com/pkg/profile" "github.com/urfave/cli" @@ -85,6 +89,11 @@ VERSION: Usage: "Enables a test http server that will handle all requests", } + startAsRosetta = cli.BoolFlag{ + Name: "rosetta", + Usage: "Starts the proxy as a rosetta server", + } + testServer *testing.TestHttpServer ) @@ -104,6 +113,7 @@ func main() { profileMode, walletKeyPemFile, testHttpServerEn, + startAsRosetta, } app.Authors = []cli.Author{ { @@ -166,26 +176,17 @@ func startProxy(ctx *cli.Context) error { return err } - stop := make(chan bool, 1) - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - epf, err := createElrondProxyFacade(ctx, generalConfig, economicsConfig, externalConfig) if err != nil { return err } - startWebServer(epf, generalConfig.GeneralSettings.ServerPort) - - go func() { - <-sigs - log.Info("terminating at user's signal...") - stop <- true - }() - - log.Info("Application is now running...") - <-stop + httpServer, err := startWebServer(epf, ctx, generalConfig) + if err != nil { + return err + } + waitForServerShutdown(httpServer) return nil } @@ -272,10 +273,11 @@ func createElrondProxyFacade( AddressPubkeyConverter: cfg.AddressPubkeyConverter, } - return createFacade(testCfg, ecCfg, exCfg, ctx.GlobalString(walletKeyPemFile.Name)) + return createFacade(testCfg, ecCfg, exCfg, ctx.GlobalString(walletKeyPemFile.Name), false) } - return createFacade(cfg, ecCfg, exCfg, ctx.GlobalString(walletKeyPemFile.Name)) + isRosettaOn := ctx.GlobalBool(startAsRosetta.Name) + return createFacade(cfg, ecCfg, exCfg, ctx.GlobalString(walletKeyPemFile.Name), isRosettaOn) } func createFacade( @@ -283,12 +285,22 @@ func createFacade( ecConf *erdConfig.EconomicsConfig, exCfg *erdConfig.ExternalConfig, pemFileLocation string, + isRosettaOn bool, ) (*facade.ElrondProxyFacade, error) { pubKeyConverter, err := factory.NewPubkeyConverter(cfg.AddressPubkeyConverter) if err != nil { return nil, err } + marshalizer, err := marshalFactory.NewMarshalizer(cfg.Marshalizer.Type) + if err != nil { + return nil, err + } + hasher, err := hasherFactory.NewHasher(cfg.Hasher.Type) + if err != nil { + return nil, err + } + shardCoord, err := getShardCoordinator(cfg) if err != nil { return nil, err @@ -344,7 +356,7 @@ func createFacade( return nil, err } - txProc, err := process.NewTransactionProcessor(bp, pubKeyConverter) + txProc, err := process.NewTransactionProcessor(bp, pubKeyConverter, hasher, marshalizer) if err != nil { return nil, err } @@ -361,7 +373,9 @@ func createFacade( if err != nil { return nil, err } - htbProc.StartCacheUpdate() + if !isRosettaOn { + htbProc.StartCacheUpdate() + } valStatsCacher := cache.NewValidatorsStatsMemoryCacher() cacheValidity = time.Duration(cfg.GeneralSettings.ValStatsCacheValidityDurationSec) * time.Second @@ -370,7 +384,9 @@ func createFacade( if err != nil { return nil, err } - valStatsProc.StartCacheUpdate() + if !isRosettaOn { + valStatsProc.StartCacheUpdate() + } nodeStatusProc, err := process.NewNodeStatusProcessor(bp) if err != nil { @@ -382,7 +398,7 @@ func createFacade( return nil, err } - return facade.NewElrondProxyFacade(accntProc, txProc, scQueryProc, htbProc, valStatsProc, faucetProc, nodeStatusProc, blockProc) + return facade.NewElrondProxyFacade(accntProc, txProc, scQueryProc, htbProc, valStatsProc, faucetProc, nodeStatusProc, blockProc, pubKeyConverter) } func createElasticSearchConnector(exCfg *erdConfig.ExternalConfig) (process.ExternalStorageConnector, error) { @@ -415,11 +431,41 @@ func getShardCoordinator(cfg *config.Config) (sharding.Coordinator, error) { return shardCoordinator, nil } -func startWebServer(proxyHandler api.ElrondProxyHandler, port int) { +func startWebServer(proxyHandler api.ElrondProxyHandler, cliContext *cli.Context, generalConfig *config.Config) (*http.Server, error) { + var err error + var httpServer *http.Server + + port := generalConfig.GeneralSettings.ServerPort + asRosetta := cliContext.GlobalBool(startAsRosetta.Name) + if asRosetta { + httpServer, err = rosetta.CreateServer(proxyHandler, generalConfig, port) + } else { + httpServer, err = api.CreateServer(proxyHandler, port) + } + if err != nil { + return nil, err + } + go func() { - err := api.Start(proxyHandler, port) - log.LogIfError(err) + err = httpServer.ListenAndServe() + if err != nil { + log.Error("cannot ListenAndServe()", "err", err) + os.Exit(1) + } }() + + return httpServer, nil +} + +func waitForServerShutdown(httpServer *http.Server) { + quit := make(chan os.Signal) + signal.Notify(quit, os.Interrupt, os.Kill) + <-quit + + shutdownContext, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _ = httpServer.Shutdown(shutdownContext) + _ = httpServer.Close() } func removeLogColors() { diff --git a/config/config.go b/config/config.go index a3cef4c0..90e30eac 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,8 @@ type GeneralSettingsConfig struct { type Config struct { GeneralSettings GeneralSettingsConfig AddressPubkeyConverter config.PubkeyConfig + Marshalizer config.TypeConfig + Hasher config.TypeConfig Observers []*data.NodeData FullHistoryNodes []*data.NodeData } diff --git a/facade/elrondProxyFacade.go b/facade/elrondProxyFacade.go index 420730b8..45b72de6 100644 --- a/facade/elrondProxyFacade.go +++ b/facade/elrondProxyFacade.go @@ -39,6 +39,8 @@ type ElrondProxyFacade struct { faucetProc FaucetProcessor nodeStatusProc NodeStatusProcessor blockProc BlockProcessor + + pubKeyConverter core.PubkeyConverter } // NewElrondProxyFacade creates a new ElrondProxyFacade instance @@ -51,6 +53,7 @@ func NewElrondProxyFacade( faucetProc FaucetProcessor, nodeStatusProc NodeStatusProcessor, blockProc BlockProcessor, + pubKeyConverter core.PubkeyConverter, ) (*ElrondProxyFacade, error) { if accountProc == nil { @@ -79,14 +82,15 @@ func NewElrondProxyFacade( } return &ElrondProxyFacade{ - accountProc: accountProc, - txProc: txProc, - scQueryService: scQueryService, - heartbeatProc: heartbeatProc, - valStatsProc: valStatsProc, - faucetProc: faucetProc, - nodeStatusProc: nodeStatusProc, - blockProc: blockProc, + accountProc: accountProc, + txProc: txProc, + scQueryService: scQueryService, + heartbeatProc: heartbeatProc, + valStatsProc: valStatsProc, + faucetProc: faucetProc, + nodeStatusProc: nodeStatusProc, + blockProc: blockProc, + pubKeyConverter: pubKeyConverter, }, nil } @@ -236,6 +240,11 @@ func (epf *ElrondProxyFacade) GetNetworkStatusMetrics(shardID uint32) (*data.Gen return epf.nodeStatusProc.GetNetworkStatusMetrics(shardID) } +// GetNetworkStatusMetrics retrieves the node's network metrics for a given shard +func (epf *ElrondProxyFacade) GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) { + return epf.nodeStatusProc.GetEconomicsDataMetrics() +} + // GetBlockByHash retrieves the block by hash for a given shard func (epf *ElrondProxyFacade) GetBlockByHash(shardID uint32, hash string, withTxs bool) (*data.BlockApiResponse, error) { return epf.blockProc.GetBlockByHash(shardID, hash, withTxs) @@ -270,3 +279,18 @@ func (epf *ElrondProxyFacade) ValidatorStatistics() (map[string]*data.ValidatorA func (epf *ElrondProxyFacade) GetAtlasBlockByShardIDAndNonce(shardID uint32, nonce uint64) (data.AtlasBlock, error) { return epf.blockProc.GetAtlasBlockByShardIDAndNonce(shardID, nonce) } + +// GetAddressConverter returns the address converter +func (epf *ElrondProxyFacade) GetAddressConverter() (core.PubkeyConverter, error) { + return epf.pubKeyConverter, nil +} + +// GetLatestFullySynchronizedHyperblockNonce returns the latest fully synchronized hyperblock nonce +func (epf *ElrondProxyFacade) GetLatestFullySynchronizedHyperblockNonce() (uint64, error) { + return epf.nodeStatusProc.GetLatestFullySynchronizedHyperblockNonce() +} + +// ComputeTransactionHash will compute hash of a given transaction +func (epf *ElrondProxyFacade) ComputeTransactionHash(tx *data.Transaction) (string, error) { + return epf.txProc.ComputeTransactionHash(tx) +} diff --git a/facade/elrondProxyFacade_test.go b/facade/elrondProxyFacade_test.go index d58fdb41..680f1579 100644 --- a/facade/elrondProxyFacade_test.go +++ b/facade/elrondProxyFacade_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ElrondNetwork/elrond-go/core" + "github.com/ElrondNetwork/elrond-go/core/pubkeyConverter" "github.com/ElrondNetwork/elrond-go/crypto" "github.com/ElrondNetwork/elrond-go/crypto/signing" "github.com/ElrondNetwork/elrond-go/crypto/signing/ed25519" @@ -15,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" ) +var publicKeyConverter, _ = pubkeyConverter.NewBech32PubkeyConverter(32) + func TestNewElrondProxyFacade_NilAccountProcShouldErr(t *testing.T) { t.Parallel() @@ -27,6 +30,7 @@ func TestNewElrondProxyFacade_NilAccountProcShouldErr(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -45,6 +49,7 @@ func TestNewElrondProxyFacade_NilTransactionProcShouldErr(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -63,6 +68,7 @@ func TestNewElrondProxyFacade_NilGetValuesProcShouldErr(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -81,6 +87,7 @@ func TestNewElrondProxyFacade_NilHeartbeatProcShouldErr(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -99,6 +106,7 @@ func TestNewElrondProxyFacade_NilValStatsProcShouldErr(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -117,6 +125,7 @@ func TestNewElrondProxyFacade_NilFaucetProcShouldErr(t *testing.T) { nil, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -135,6 +144,7 @@ func TestNewElrondProxyFacade_NilNodeProcessor(t *testing.T) { &mock.FaucetProcessorStub{}, nil, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.Nil(t, epf) @@ -153,6 +163,7 @@ func TestNewElrondProxyFacade_ShouldWork(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) assert.NotNil(t, epf) @@ -177,6 +188,7 @@ func TestElrondProxyFacade_GetAccount(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) _, _ = epf.GetAccount("") @@ -203,6 +215,7 @@ func TestElrondProxyFacade_SendTransaction(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) _, _, _ = epf.SendTransaction(&data.Transaction{}) @@ -228,6 +241,7 @@ func TestElrondProxyFacade_SimulateTransaction(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) _, _ = epf.SimulateTransaction(&data.Transaction{}) @@ -277,6 +291,7 @@ func TestElrondProxyFacade_SendUserFunds(t *testing.T) { }, }, &mock.BlockProcessorStub{}, + publicKeyConverter, ) _ = epf.SendUserFunds("", big.NewInt(0)) @@ -302,6 +317,7 @@ func TestElrondProxyFacade_GetDataValue(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) _, _ = epf.ExecuteSCQuery(nil) @@ -333,6 +349,7 @@ func TestElrondProxyFacade_GetHeartbeatData(t *testing.T) { &mock.FaucetProcessorStub{}, &mock.NodeStatusProcessorStub{}, &mock.BlockProcessorStub{}, + publicKeyConverter, ) actualResult, _ := epf.GetHeartbeatData() diff --git a/facade/interface.go b/facade/interface.go index 4a9fa122..53ec667a 100644 --- a/facade/interface.go +++ b/facade/interface.go @@ -25,6 +25,7 @@ type TransactionProcessor interface { GetTransactionStatus(txHash string, sender string) (string, error) GetTransaction(txHash string) (*data.FullTransaction, error) GetTransactionByHashAndSenderAddress(txHash string, sndAddr string) (*data.FullTransaction, int, error) + ComputeTransactionHash(tx *data.Transaction) (string, error) } // SCQueryService defines how data should be get from a SC account @@ -46,6 +47,8 @@ type ValidatorStatisticsProcessor interface { type NodeStatusProcessor interface { GetNetworkConfigMetrics() (*data.GenericAPIResponse, error) GetNetworkStatusMetrics(shardID uint32) (*data.GenericAPIResponse, error) + GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) + GetLatestFullySynchronizedHyperblockNonce() (uint64, error) } // BlockProcessor defines what a block processor should do diff --git a/facade/mock/nodeStatusProcessorStub.go b/facade/mock/nodeStatusProcessorStub.go index dd251c06..2c118fb4 100644 --- a/facade/mock/nodeStatusProcessorStub.go +++ b/facade/mock/nodeStatusProcessorStub.go @@ -4,8 +4,10 @@ import "github.com/ElrondNetwork/elrond-proxy-go/data" // NodeStatusProcessorStub -- type NodeStatusProcessorStub struct { - GetConfigMetricsCalled func() (*data.GenericAPIResponse, error) - GetNetworkMetricsCalled func(shardID uint32) (*data.GenericAPIResponse, error) + GetConfigMetricsCalled func() (*data.GenericAPIResponse, error) + GetNetworkMetricsCalled func(shardID uint32) (*data.GenericAPIResponse, error) + GetLatestBlockNonceCalled func() (uint64, error) + GetEconomicsDataMetricsCalled func() (*data.GenericAPIResponse, error) } // GetNetworkConfigMetrics -- @@ -17,3 +19,13 @@ func (nsps *NodeStatusProcessorStub) GetNetworkConfigMetrics() (*data.GenericAPI func (nsps *NodeStatusProcessorStub) GetNetworkStatusMetrics(shardID uint32) (*data.GenericAPIResponse, error) { return nsps.GetNetworkMetricsCalled(shardID) } + +// GetEconomicsDataMetrics -- +func (nsps *NodeStatusProcessorStub) GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) { + return nsps.GetEconomicsDataMetricsCalled() +} + +// GetLatestBlockNonce - +func (nsps *NodeStatusProcessorStub) GetLatestFullySynchronizedHyperblockNonce() (uint64, error) { + return nsps.GetLatestBlockNonceCalled() +} diff --git a/facade/mock/transactionProcessorStub.go b/facade/mock/transactionProcessorStub.go index dda9886f..1fd96896 100644 --- a/facade/mock/transactionProcessorStub.go +++ b/facade/mock/transactionProcessorStub.go @@ -16,6 +16,7 @@ type TransactionProcessorStub struct { GetTransactionStatusHandler func(txHash string, sender string) (string, error) GetTransactionCalled func(txHash string) (*data.FullTransaction, error) GetTransactionByHashAndSenderAddressCalled func(txHash string, sndAddr string) (*data.FullTransaction, int, error) + ComputeTransactionHashCalled func(tx *data.Transaction) (string, error) } // SimulateTransaction - @@ -33,6 +34,11 @@ func (tps *TransactionProcessorStub) SendMultipleTransactions(txs []*data.Transa return tps.SendMultipleTransactionsCalled(txs) } +// ComputeTransactionHash - +func (tps *TransactionProcessorStub) ComputeTransactionHash(tx *data.Transaction) (string, error) { + return tps.ComputeTransactionHashCalled(tx) +} + // SendUserFunds - func (tps *TransactionProcessorStub) SendUserFunds(receiver string, value *big.Int) error { return tps.SendUserFundsCalled(receiver, value) diff --git a/go.mod b/go.mod index c5b23a5c..929dac9c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/ElrondNetwork/elrond-go v1.1.1-0.20200914111912-d1792d662b8f github.com/ElrondNetwork/elrond-go-logger v1.0.4 github.com/ElrondNetwork/elrond-vm-common v0.1.23 + github.com/coinbase/rosetta-sdk-go v0.4.8 github.com/elastic/go-elasticsearch/v7 v7.1.0 github.com/gin-contrib/cors v0.0.0-20190301062745-f9e10995c85a github.com/gin-gonic/gin v1.6.3 diff --git a/go.sum b/go.sum index 7a7f3540..0875cc12 100644 --- a/go.sum +++ b/go.sum @@ -9,13 +9,25 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +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= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/ElrondNetwork/arwen-wasm-vm v0.3.35 h1:VVRqYmITw80soVUR3oayQWZPt8N7yNxbED/g6DEIFqw= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/ElrondNetwork/arwen-wasm-vm v0.3.35/go.mod h1:Ph31oyKl4TQv4hqqPe3XENkccUu2IRgp+7ww91SF/zg= -github.com/ElrondNetwork/big-int-util v0.1.0 h1:vTMoJ5azhVmr7jhpSD3JUjQdkdyXoEPVkOvhdw1RjV4= github.com/ElrondNetwork/big-int-util v0.1.0/go.mod h1:96viBvoTXLjZOhEvE0D+QnAwg1IJLPAK6GVHMbC7Aw4= -github.com/ElrondNetwork/concurrent-map v0.1.3 h1:j2LtPrNJuerannC1cQDE79STvi/P04gd61sNYo04Zf0= github.com/ElrondNetwork/concurrent-map v0.1.3/go.mod h1:3XwSwn4JHI0lrKxWLZvtp53Emr8BXYTmNQGwcukHJEE= github.com/ElrondNetwork/elrond-go v1.1.1-0.20200914111912-d1792d662b8f h1:MLakjh6ZX09+b7Tc/SkfhR67INImTyPMEtR9e/tUxSM= github.com/ElrondNetwork/elrond-go v1.1.1-0.20200914111912-d1792d662b8f/go.mod h1:7JZz4Jad6LpHgOBmm1ChLgf2QRxa8yVQZc9RDDLQZpU= @@ -30,32 +42,60 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= +github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/coinbase/rosetta-sdk-go v0.3.3 h1:69Ncq9GeyVEANJexd72IdjN249Hn6Dntb7KmHoct5WU= +github.com/coinbase/rosetta-sdk-go v0.3.3/go.mod h1:xTq9qdqHVg6uA87pMUJLE+PqyXFM4PyqOY79J8MCNGA= +github.com/coinbase/rosetta-sdk-go v0.3.4 h1:jWKgajozco/T0FNnZb2TqBsmsUoF6ZuCLnUJkEE+vNg= +github.com/coinbase/rosetta-sdk-go v0.3.4/go.mod h1:Q6dAY0kdG2X3jNaIYnkxnZOb8XEZQar9Q1RcnBgm/wQ= +github.com/coinbase/rosetta-sdk-go v0.4.4 h1:zTUS4bVlTfD4xq/o6JtsuU+g9sf3+S3Nnn2A24Ycow4= +github.com/coinbase/rosetta-sdk-go v0.4.4/go.mod h1:Luv0AhzZH81eul2hYZ3w0hBGwmFPiexwbntYxihEZck= +github.com/coinbase/rosetta-sdk-go v0.4.8 h1:+E1TM4q1c5/x/jE9FPI1IZIbNvUWy4tRRgDPLnKzUV4= +github.com/coinbase/rosetta-sdk-go v0.4.8/go.mod h1:8d4iN4VSGvLUzl+jRQlvYSLyS9TeY0QZebneWouizqU= +github.com/coinbase/rosetta-sdk-go v0.5.0 h1:WdHJJ7RSUJ1sXwxVAoUb/owH3GYVEK2K3GURIZT3NOA= +github.com/coinbase/rosetta-sdk-go v0.5.0/go.mod h1:QVVeKHWFNb0NyzEY06LxXMAylJkYa7n+Hk03pORr0ws= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -65,27 +105,57 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/dave/dst v0.23.1/go.mod h1:LjPcLEauK4jC5hQ1fE/wr05O41zK91Pr4Qs22Ljq7gs= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8= +github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= +github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elastic/go-elasticsearch/v7 v7.1.0 h1:BLm6CaiURXtycMTHpnJrx/zfoGbztMQi6XlcTwayJuU= github.com/elastic/go-elasticsearch/v7 v7.1.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.9.15/go.mod h1:slT8bPPRhXsyNTwHQxrOnjuTZ1sDXRajW11EkJ84QJ0= +github.com/ethereum/go-ethereum v1.9.18/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg= +github.com/ethereum/go-ethereum v1.9.21/go.mod h1:RXAVzbGrSGmDkDnHymruTAIEjUR3E4TX0EOpaj702sI= +github.com/ethereum/go-ethereum v1.9.22/go.mod h1:FQjK3ZwD8C5DYn7ukTmFee36rq1dOMESiUfXr5RUc1w= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/cors v0.0.0-20190301062745-f9e10995c85a h1:zBycVvXa03SIX+jdMv8wGu9TMDMWdN8EhaR1FoeKHNo= github.com/gin-contrib/cors v0.0.0-20190301062745-f9e10995c85a/go.mod h1:pL2kNE+DgDU+eQ+dary5bX0Z6LPP8nR6Mqs1iejILw4= @@ -101,9 +171,10 @@ github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmy github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -111,6 +182,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -125,7 +198,10 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -133,30 +209,45 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/addlicense v0.0.0-20200622132530-df58acafd6d5/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= +github.com/google/addlicense v0.0.0-20200827091314-d1655b921368/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gops v0.3.6/go.mod h1:RZ1rH95wsAGX4vMWKmqBOIWynmWisBf4QFdgT/k/xOI= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= @@ -165,14 +256,16 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/herumi/bls-go-binary v0.0.0-20200324054641-17de9ae04665/go.mod h1:O4Vp1AfR4raRGwFeQpr9X/PQtncEicMoOe6BQt1oX0Y= +github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -210,6 +303,7 @@ github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscw github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= @@ -221,13 +315,16 @@ github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZl github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ= @@ -236,14 +333,15 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= @@ -392,6 +490,7 @@ github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZ github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= +github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -399,12 +498,23 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= @@ -419,8 +529,11 @@ github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1 github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -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.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -471,31 +584,41 @@ github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= @@ -504,19 +627,35 @@ github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6J github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/golines v0.0.0-20200306054842-869934f8da7b/go.mod h1:K7zjgP8yJ/U8nb8nxaSykalAKSvbqr6TNbd9B7zzBFU= +github.com/segmentio/golines v0.0.0-20200824192126-7f30d3046793/go.mod h1:bQSh5qdVR67XiCKbaVvYO41s50c5hQo+3cY/1CQQ3xQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984 h1:wsZAb4P8F7uQSwsnxE1gk9AHCcc5U0wvyDzcLwFY0Eo= github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= +github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible h1:+gAR1bMhuoQnZMTWFIvp7ukynULPsteLzG+siZKLtD8= +github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= +github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= @@ -543,6 +682,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= 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/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -557,6 +697,9 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -567,19 +710,33 @@ github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= +github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wangjia184/sortedset v0.0.0-20160527075905-f5d03557ba30/go.mod h1:YkocrP2K2tcw938x9gCOmT5G5eCD6jsTz0SZuyAqwIE= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= @@ -588,9 +745,13 @@ github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvX github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= @@ -607,10 +768,12 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -622,18 +785,33 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +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.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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -648,10 +826,18 @@ golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 h1:E7ct1C6/33eOdrGZKMoyntcEvs2dwZnDe30crG5vpYU= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -662,12 +848,16 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -677,43 +867,66 @@ golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191024172528-b4ff53e7a1cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191024220359-3d91e92cde03/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729181040-64cdafbe085c/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200731060945-b5fad4ed8dd6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools/gopls v0.4.4/go.mod h1:zhyGzA+CAtREUwwq/btQxEx2FHnGzDwJvGs5YqdVCbE= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -721,6 +934,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -728,45 +942,65 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200603215123-a4a8cb9d2cbc/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/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/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= +mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws= +mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8= rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/process/errors.go b/process/errors.go index 3dc18d37..94dda705 100644 --- a/process/errors.go +++ b/process/errors.go @@ -64,3 +64,21 @@ var ErrNoValidTransactionToSend = errors.New("no valid transaction to send") // ErrNilDatabaseConnector signals that a nil database connector was provided var ErrNilDatabaseConnector = errors.New("not valid database connector") + +// ErrCannotParseNodeStatusMetrics signals that the node status metrics cannot be parsed +var ErrCannotParseNodeStatusMetrics = errors.New("cannot parse node status metrics") + +// ErrNilHasher is raised when a valid hasher is expected but nil used +var ErrNilHasher = errors.New("hasher is nil") + +// ErrNilMarshalizer is raised when a valid marshalizer is expected but nil used +var ErrNilMarshalizer = errors.New("marshalizer is nil") + +// ErrInvalidTransactionValueField signals that field value of transaction is invalid +var ErrInvalidTransactionValueField = errors.New("invalid transaction value field") + +// ErrInvalidAddress signals that an invalid address has been provided +var ErrInvalidAddress = errors.New("could not create address from provided param") + +// ErrInvalidSignatureBytes signal that an invalid signature hash been provided +var ErrInvalidSignatureBytes = errors.New("invalid signatures bytes") diff --git a/process/nodeStatusProcessor.go b/process/nodeStatusProcessor.go index cfa842a8..a698c1f6 100644 --- a/process/nodeStatusProcessor.go +++ b/process/nodeStatusProcessor.go @@ -1,6 +1,12 @@ package process import ( + "errors" + "math" + "strconv" + "strings" + + "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-go/core/check" "github.com/ElrondNetwork/elrond-proxy-go/data" ) @@ -11,6 +17,12 @@ const NetworkStatusPath = "/network/status" // NetworkConfigPath represents the path where an observer exposes his network metrics const NetworkConfigPath = "/network/config" +// EconomicsDataPath represents the path where an observer exposes his economics data +const EconomicsDataPath = "/network/economics" + +// NetworkConfigPath represents the path where an observer exposes his node status metrics +const NodeStatusPath = "/node/status" + // NodeStatusProcessor handles the action needed for fetching data related to status metrics from nodes type NodeStatusProcessor struct { proc Processor @@ -74,3 +86,203 @@ func (nsp *NodeStatusProcessor) GetNetworkConfigMetrics() (*data.GenericAPIRespo return nil, ErrSendingRequest } + +// GetNetworkConfigMetrics will simply forward the network config metrics from an observer in the given shard +func (nsp *NodeStatusProcessor) GetEconomicsDataMetrics() (*data.GenericAPIResponse, error) { + metaObservers, err := nsp.proc.GetObservers(core.MetachainShardId) + if err != nil { + return nil, err + } + + metaResponse, err := nsp.getEconomicsDataMetrics(metaObservers) + if err == nil { + return metaResponse, nil + } + + log.Warn("cannot get economics data metrics from metachain observer. will try with all observers", + "error", err) + + allObservers, err := nsp.proc.GetAllObservers() + if err != nil { + return nil, err + } + + return nsp.getEconomicsDataMetrics(allObservers) +} + +func (nsp *NodeStatusProcessor) getEconomicsDataMetrics(observers []*data.NodeData) (*data.GenericAPIResponse, error) { + for _, observer := range observers { + var responseNetworkMetrics *data.GenericAPIResponse + + _, err := nsp.proc.CallGetRestEndPoint(observer.Address, EconomicsDataPath, &responseNetworkMetrics) + if err != nil { + log.Error("economics data request", "observer", observer.Address, "error", err.Error()) + continue + } + + log.Info("economics data request", "shard id", observer.ShardId, "observer", observer.Address) + return responseNetworkMetrics, nil + } + + return nil, ErrSendingRequest +} + +func (nsp *NodeStatusProcessor) getNodeStatusMetrics(shardID uint32) (*data.GenericAPIResponse, error) { + observers, err := nsp.proc.GetObservers(shardID) + if err != nil { + return nil, err + } + + for _, observer := range observers { + var responseNetworkMetrics *data.GenericAPIResponse + + _, err := nsp.proc.CallGetRestEndPoint(observer.Address, NodeStatusPath, &responseNetworkMetrics) + if err != nil { + log.Error("node status metrics request", "observer", observer.Address, "error", err.Error()) + continue + } + + log.Info("node status metrics request", "shard id", observer.ShardId, "observer", observer.Address) + return responseNetworkMetrics, nil + + } + + return nil, ErrSendingRequest +} + +// GetLatestFullySynchronizedHyperblockNonce will compute nonce of the latest hyperblock that can be returned +func (nsp *NodeStatusProcessor) GetLatestFullySynchronizedHyperblockNonce() (uint64, error) { + shardsIDs, err := nsp.getShardsIDs() + if err != nil { + return 0, err + } + + nonces := make([]uint64, 0) + for shardID := range shardsIDs { + nodeStatusResponse, err := nsp.getNodeStatusMetrics(shardID) + if err != nil { + return 0, err + } + + if nodeStatusResponse.Error != "" { + return 0, errors.New(nodeStatusResponse.Error) + } + + var nonce uint64 + var ok bool + if shardID == core.MetachainShardId { + nonce, ok = getNonceFromMetachainStatus(nodeStatusResponse.Data) + } else { + nonce, ok = getNonceFromShardStatus(nodeStatusResponse.Data) + } + if !ok { + return 0, ErrCannotParseNodeStatusMetrics + } + + nonces = append(nonces, nonce) + } + + return getMinNonce(nonces), nil +} + +func getMinNonce(noncesSlice []uint64) uint64 { + // initialize min with max uint64 value + min := uint64(math.MaxUint64) + for _, value := range noncesSlice { + if value < min { + min = value + } + } + + return min +} + +func (nsp *NodeStatusProcessor) getShardsIDs() (map[uint32]struct{}, error) { + observers, err := nsp.proc.GetAllObservers() + if err != nil { + return nil, err + } + + shardsIDs := make(map[uint32]struct{}) + for _, observer := range observers { + shardsIDs[observer.ShardId] = struct{}{} + } + + if len(shardsIDs) == 0 { + return nil, ErrMissingObserver + } + + return shardsIDs, nil +} + +func getNonceFromShardStatus(nodeStatusData interface{}) (uint64, bool) { + metric, ok := getMetric(nodeStatusData, core.MetricCrossCheckBlockHeight) + if !ok { + return 0, false + } + + return parseMetricCrossCheckBlockHeight(metric) +} + +func getNonceFromMetachainStatus(nodeStatusData interface{}) (uint64, bool) { + metric, ok := getMetric(nodeStatusData, core.MetricNonce) + if !ok { + return 0, false + } + + return getUint(metric), true +} + +func getMetric(nodeStatusData interface{}, metric string) (interface{}, bool) { + metricsMapI, ok := nodeStatusData.(map[string]interface{}) + if !ok { + return nil, false + } + + metricsMap, ok := metricsMapI["metrics"] + if !ok { + return nil, false + } + + metrics, ok := metricsMap.(map[string]interface{}) + if !ok { + return nil, false + } + + value, ok := metrics[metric] + if !ok { + return nil, false + } + + return value, true +} + +func parseMetricCrossCheckBlockHeight(value interface{}) (uint64, bool) { + valueStr, ok := value.(string) + if !ok { + return 0, false + } + + // metric looks like that + // "meta 886717" + values := strings.Split(valueStr, " ") + if len(values) < 2 { + return 0, false + } + + nonce, err := strconv.ParseUint(values[1], 10, 64) + if err != nil { + return 0, false + } + + return nonce, true +} + +func getUint(value interface{}) uint64 { + valueFloat, ok := value.(float64) + if !ok { + return 0 + } + + return uint64(valueFloat) +} diff --git a/process/nodeStatusProcessor_test.go b/process/nodeStatusProcessor_test.go index 7aeba7fe..dcff656e 100644 --- a/process/nodeStatusProcessor_test.go +++ b/process/nodeStatusProcessor_test.go @@ -5,6 +5,7 @@ import ( "errors" "testing" + "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-proxy-go/data" "github.com/ElrondNetwork/elrond-proxy-go/process/mock" "github.com/stretchr/testify/require" @@ -139,3 +140,112 @@ func TestNodeStatusProcessor_GetNetworkMetrics(t *testing.T) { require.Equal(t, 1, int(valueFromMap.(float64))) } + +func TestNodeStatusProcessor_GetLatestBlockNonce(t *testing.T) { + t.Parallel() + + nodeStatusProc, _ := NewNodeStatusProcessor(&mock.ProcessorStub{ + GetAllObserversCalled: func() (observers []*data.NodeData, err error) { + return []*data.NodeData{ + {Address: "address1", ShardId: 0}, + {Address: "address2", ShardId: core.MetachainShardId}, + }, nil + }, + GetObserversCalled: func(shardId uint32) ([]*data.NodeData, error) { + if shardId == 0 { + return []*data.NodeData{ + {Address: "address1", ShardId: 0}, + }, nil + } else { + return []*data.NodeData{ + {Address: "address2", ShardId: core.MetachainShardId}, + }, nil + } + }, + + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + + var localMap map[string]interface{} + if address == "address1" { + localMap = map[string]interface{}{ + "metrics": map[string]interface{}{ + core.MetricCrossCheckBlockHeight: "meta 123", + }, + } + } else { + localMap = map[string]interface{}{ + "metrics": map[string]interface{}{ + core.MetricNonce: 122, + }, + } + } + + genericResp := &data.GenericAPIResponse{Data: localMap} + genRespBytes, _ := json.Marshal(genericResp) + + return 0, json.Unmarshal(genRespBytes, value) + }, + }) + + nonce, err := nodeStatusProc.GetLatestFullySynchronizedHyperblockNonce() + require.NoError(t, err) + require.Equal(t, uint64(122), nonce) +} + +func TestNodeStatusProcessor_GetEconomicsDataMetricsGetRestEndPointErrorOnMetaShouldTryOnShard(t *testing.T) { + t.Parallel() + + addressMeta := "address_meta" + addressShard := "address_shard" + shardNodeWasCalled := false + + localErr := errors.New("local error") + nodeStatusProc, _ := NewNodeStatusProcessor(&mock.ProcessorStub{ + GetObserversCalled: func(shardId uint32) (observers []*data.NodeData, err error) { + return []*data.NodeData{ + {Address: addressShard, ShardId: 0}, + {Address: addressMeta, ShardId: core.MetachainShardId}, + }, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + if address == addressMeta { + return 0, localErr + } + if address == addressShard { + shardNodeWasCalled = true + } + return 200, nil + }, + }) + + _, err := nodeStatusProc.GetEconomicsDataMetrics() + require.NoError(t, err) + require.True(t, shardNodeWasCalled) +} + +func TestNodeStatusProcessor_GetEconomicsDataMetricsShouldWork(t *testing.T) { + t.Parallel() + + addressMeta := "address_meta" + expectedResponse := &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "erd_total_supply": "12345", + }, + } + + nodeStatusProc, _ := NewNodeStatusProcessor(&mock.ProcessorStub{ + GetObserversCalled: func(shardId uint32) (observers []*data.NodeData, err error) { + return []*data.NodeData{ + {Address: addressMeta, ShardId: core.MetachainShardId}, + }, nil + }, + CallGetRestEndPointCalled: func(_ string, _ string, value interface{}) (int, error) { + expectedResponseBytes, _ := json.Marshal(expectedResponse) + return 200, json.Unmarshal(expectedResponseBytes, value) + }, + }) + + actualResponse, err := nodeStatusProc.GetEconomicsDataMetrics() + require.NoError(t, err) + require.Equal(t, *expectedResponse, *actualResponse) +} diff --git a/process/transactionProcessor.go b/process/transactionProcessor.go index 6bb1cecc..3a170045 100644 --- a/process/transactionProcessor.go +++ b/process/transactionProcessor.go @@ -3,11 +3,15 @@ package process import ( "encoding/hex" "fmt" + "math/big" "net/http" "strconv" "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-go/core/check" + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-go/hashing" + "github.com/ElrondNetwork/elrond-go/marshal" "github.com/ElrondNetwork/elrond-proxy-go/api/errors" "github.com/ElrondNetwork/elrond-proxy-go/data" ) @@ -47,12 +51,16 @@ type erdTransaction struct { type TransactionProcessor struct { proc Processor pubKeyConverter core.PubkeyConverter + hasher hashing.Hasher + marshalizer marshal.Marshalizer } // NewTransactionProcessor creates a new instance of TransactionProcessor func NewTransactionProcessor( proc Processor, pubKeyConverter core.PubkeyConverter, + hasher hashing.Hasher, + marshalizer marshal.Marshalizer, ) (*TransactionProcessor, error) { if check.IfNil(proc) { return nil, ErrNilCoreProcessor @@ -60,10 +68,18 @@ func NewTransactionProcessor( if check.IfNil(pubKeyConverter) { return nil, ErrNilPubKeyConverter } + if check.IfNil(hasher) { + return nil, ErrNilHasher + } + if check.IfNil(marshalizer) { + return nil, ErrNilMarshalizer + } return &TransactionProcessor{ proc: proc, pubKeyConverter: pubKeyConverter, + hasher: hasher, + marshalizer: marshalizer, }, nil } @@ -518,6 +534,49 @@ func (tp *TransactionProcessor) checkTransactionFields(tx *data.Transaction) err return nil } +// ComputeTransactionHash will compute hash of a given transaction +// TODO move to node +func (tp *TransactionProcessor) ComputeTransactionHash(tx *data.Transaction) (string, error) { + valueBig, ok := big.NewInt(0).SetString(tx.Value, 10) + if !ok { + return "", ErrInvalidTransactionValueField + } + receiverAddress, err := tp.pubKeyConverter.Decode(tx.Receiver) + if err != nil { + return "", ErrInvalidAddress + } + + senderAddress, err := tp.pubKeyConverter.Decode(tx.Sender) + if err != nil { + return "", ErrInvalidAddress + } + + signatureBytes, err := hex.DecodeString(tx.Signature) + if err != nil { + return "", ErrInvalidSignatureBytes + } + + regularTx := &transaction.Transaction{ + Nonce: tx.Nonce, + Value: valueBig, + RcvAddr: receiverAddress, + SndAddr: senderAddress, + GasPrice: tx.GasPrice, + GasLimit: tx.GasLimit, + Data: tx.Data, + ChainID: []byte(tx.ChainID), + Version: tx.Version, + Signature: signatureBytes, + } + + txHash, err := core.CalculateHash(tp.marshalizer, tp.hasher, regularTx) + if err != nil { + return "", nil + } + + return hex.EncodeToString(txHash), nil +} + func (tp *TransactionProcessor) getObserversOrFullHistoryNodes() ([]*data.NodeData, error) { fullHistoryNodes, err := tp.proc.GetAllFullHistoryNodes() if err == nil { diff --git a/process/transactionProcessor_test.go b/process/transactionProcessor_test.go index 1bc5fd27..0fca1822 100644 --- a/process/transactionProcessor_test.go +++ b/process/transactionProcessor_test.go @@ -3,11 +3,15 @@ package process_test import ( "encoding/hex" "errors" + "math/big" "net/http" "sync/atomic" "testing" + "github.com/ElrondNetwork/elrond-go/core" "github.com/ElrondNetwork/elrond-go/data/transaction" + hasherFactory "github.com/ElrondNetwork/elrond-go/hashing/factory" + marshalFactory "github.com/ElrondNetwork/elrond-go/marshal/factory" "github.com/ElrondNetwork/elrond-proxy-go/data" "github.com/ElrondNetwork/elrond-proxy-go/process" "github.com/ElrondNetwork/elrond-proxy-go/process/mock" @@ -15,10 +19,13 @@ import ( "github.com/stretchr/testify/require" ) +var hasher, _ = hasherFactory.NewHasher("blake2b") +var marshalizer, _ = marshalFactory.NewMarshalizer("gogo protobuf") + func TestNewTransactionProcessor_NilCoreProcessorShouldErr(t *testing.T) { t.Parallel() - tp, err := process.NewTransactionProcessor(nil, &mock.PubKeyConverterMock{}) + tp, err := process.NewTransactionProcessor(nil, &mock.PubKeyConverterMock{}, hasher, marshalizer) require.Nil(t, tp) require.Equal(t, process.ErrNilCoreProcessor, err) @@ -27,16 +34,34 @@ func TestNewTransactionProcessor_NilCoreProcessorShouldErr(t *testing.T) { func TestNewTransactionProcessor_NilPubKeyConverterShouldErr(t *testing.T) { t.Parallel() - tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, nil) + tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, nil, hasher, marshalizer) require.Nil(t, tp) require.Equal(t, process.ErrNilPubKeyConverter, err) } +func TestNewTransactionProcessor_NilHasherShouldErr(t *testing.T) { + t.Parallel() + + tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, nil, marshalizer) + + require.Nil(t, tp) + require.Equal(t, process.ErrNilHasher, err) +} + +func TestNewTransactionProcessor_NilMarshalizerShouldErr(t *testing.T) { + t.Parallel() + + tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, hasher, nil) + + require.Nil(t, tp) + require.Equal(t, process.ErrNilMarshalizer, err) +} + func TestNewTransactionProcessor_OkValuesShouldWork(t *testing.T) { t.Parallel() - tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}) + tp, err := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, hasher, marshalizer) require.NotNil(t, tp) require.Nil(t, err) @@ -47,7 +72,7 @@ func TestNewTransactionProcessor_OkValuesShouldWork(t *testing.T) { func TestTransactionProcessor_SendTransactionInvalidHexAdressShouldErr(t *testing.T) { t.Parallel() - tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}) + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, hasher, marshalizer) rc, txHash, err := tp.SendTransaction(&data.Transaction{ Sender: "invalid hex number", }) @@ -61,7 +86,7 @@ func TestTransactionProcessor_SendTransactionInvalidHexAdressShouldErr(t *testin func TestTransactionProcessor_SendTransactionNoChainIDShouldErr(t *testing.T) { t.Parallel() - tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}) + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, hasher, marshalizer) rc, txHash, err := tp.SendTransaction(&data.Transaction{}) require.Empty(t, txHash) @@ -73,7 +98,7 @@ func TestTransactionProcessor_SendTransactionNoChainIDShouldErr(t *testing.T) { func TestTransactionProcessor_SendTransactionNoVersionShouldErr(t *testing.T) { t.Parallel() - tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}) + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, &mock.PubKeyConverterMock{}, hasher, marshalizer) rc, txHash, err := tp.SendTransaction(&data.Transaction{ ChainID: "chainID", }) @@ -94,7 +119,7 @@ func TestTransactionProcessor_SendTransactionComputeShardIdFailsShouldErr(t *tes return 0, errExpected }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) rc, txHash, err := tp.SendTransaction(&data.Transaction{ ChainID: "chain", @@ -119,7 +144,7 @@ func TestTransactionProcessor_SendTransactionGetObserversFailsShouldErr(t *testi return nil, errExpected }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) address := "DEADBEEF" rc, txHash, err := tp.SendTransaction(&data.Transaction{ @@ -152,7 +177,7 @@ func TestTransactionProcessor_SendTransactionSendingFailsOnAllObserversShouldErr return http.StatusInternalServerError, errExpected }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) address := "DEADBEEF" rc, txHash, err := tp.SendTransaction(&data.Transaction{ @@ -188,7 +213,7 @@ func TestTransactionProcessor_SendTransactionSendingFailsOnFirstObserverShouldSt return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) address := "DEADBEEF" rc, resultedTxHash, err := tp.SendTransaction(&data.Transaction{ @@ -234,7 +259,7 @@ func TestTransactionProcessor_SendMultipleTransactionsShouldWork(t *testing.T) { return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) response, err := tp.SendMultipleTransactions(txsToSend) @@ -302,7 +327,7 @@ func TestTransactionProcessor_SendMultipleTransactionsShouldWorkAndSendTxsByShar return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) response, err := tp.SendMultipleTransactions(txsToSend) @@ -341,7 +366,7 @@ func TestTransactionProcessor_SimulateTransactionShouldWork(t *testing.T) { return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) response, err := tp.SimulateTransaction(txsToSimulate) @@ -392,7 +417,7 @@ func TestTransactionProcessor_GetTransactionStatusIntraShardTransaction(t *testi return http.StatusBadGateway, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), "") @@ -445,7 +470,7 @@ func TestTransactionProcessor_GetTransactionStatusCrossShardTransaction(t *testi return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), "") @@ -502,7 +527,7 @@ func TestTransactionProcessor_GetTransactionStatusCrossShardTransactionDestinati return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), "") @@ -566,7 +591,7 @@ func TestTransactionProcessor_GetTransactionStatusWithSenderAddressCrossShard(t return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), sndrShard0) @@ -584,7 +609,7 @@ func TestTransactionProcessor_GetTransactionStatusWithSenderInvaidSender(t *test return 0, errors.New("local error") }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), "blablabla") @@ -635,10 +660,171 @@ func TestTransactionProcessor_GetTransactionStatusWithSenderAddressIntraShard(t return http.StatusOK, nil }, }, - &mock.PubKeyConverterMock{}, + &mock.PubKeyConverterMock{}, hasher, marshalizer, ) txStatus, err := tp.GetTransactionStatus(string(hash0), sndrShard0) assert.NoError(t, err) assert.Equal(t, txResponseStatus, txStatus) } + +func TestTransactionProcessor_ComputeTransactionInvalidTransactionValue(t *testing.T) { + t.Parallel() + + tx := &data.Transaction{ + Nonce: 1, + Value: "aaaa", + Receiver: "61616161", + Sender: "62626262", + GasPrice: 1, + GasLimit: 2, + Data: []byte("blablabla"), + Signature: "abcdabcd", + ChainID: "1", + Version: 1, + } + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + _, err := tp.ComputeTransactionHash(tx) + assert.Equal(t, process.ErrInvalidTransactionValueField, err) +} + +func TestTransactionProcessor_ComputeTransactionInvalidReceiverAddress(t *testing.T) { + t.Parallel() + + tx := &data.Transaction{ + Nonce: 1, + Value: "1", + Receiver: "gfdgfd", + Sender: "62626262", + GasPrice: 1, + GasLimit: 2, + Data: []byte("blablabla"), + Signature: "abcdabcd", + ChainID: "1", + Version: 1, + } + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + _, err := tp.ComputeTransactionHash(tx) + assert.Equal(t, process.ErrInvalidAddress, err) +} + +func TestTransactionProcessor_ComputeTransactionInvalidSenderAddress(t *testing.T) { + t.Parallel() + + tx := &data.Transaction{ + Nonce: 1, + Value: "1", + Receiver: "62626262", + Sender: "gagasd", + GasPrice: 1, + GasLimit: 2, + Data: []byte("blablabla"), + Signature: "abcdabcd", + ChainID: "1", + Version: 1, + } + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + _, err := tp.ComputeTransactionHash(tx) + assert.Equal(t, process.ErrInvalidAddress, err) +} + +func TestTransactionProcessor_ComputeTransactionInvalidSignaturesBytes(t *testing.T) { + t.Parallel() + + tx := &data.Transaction{ + Nonce: 1, + Value: "1", + Receiver: "62626262", + Sender: "62626262", + GasPrice: 1, + GasLimit: 2, + Data: []byte("blablabla"), + Signature: "gfgdgfdgfd", + ChainID: "1", + Version: 1, + } + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + _, err := tp.ComputeTransactionHash(tx) + assert.Equal(t, process.ErrInvalidSignatureBytes, err) +} + +func TestTransactionProcessor_ComputeTransactionShouldWork1(t *testing.T) { + t.Parallel() + + tx := &data.Transaction{ + Nonce: 1, + Value: "1", + Receiver: "61616161", + Sender: "62626262", + GasPrice: 1, + GasLimit: 2, + Data: []byte("blablabla"), + Signature: "abcdabcd", + ChainID: "1", + Version: 1, + } + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + txHashHex := "891694ae6307ee9f17f861816187a6729268397f8fabc055d5b334f552cd3cfb" + txHash, err := tp.ComputeTransactionHash(tx) + assert.Nil(t, err) + assert.Equal(t, txHashHex, txHash) +} + +func TestTransactionProcessor_ComputeTransactionShouldWork2(t *testing.T) { + t.Parallel() + + protoTx := transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(1000), + RcvAddr: []byte("7c3f38ab6d2f961de7e5ad914cdbd0b6361b5ddb53d504b5297bfa4c901fc1d8"), + SndAddr: []byte("7c3f38ab6d2f961de7e5ad914cdbd0b6361b5ddb53d504b5297bfa4c901fc1d8"), + GasPrice: 12, + GasLimit: 13, + Data: []byte("aGVsbG8="), + ChainID: []byte("1"), + Version: 1, + Signature: []byte("5e97b3bb223acfe3a152bb8e7fec31909059c90f75b56ffc4edf1695baab561b"), + } + protoTxHashBytes, _ := core.CalculateHash(marshalizer, hasher, &protoTx) + protoTxHash := hex.EncodeToString(protoTxHashBytes) + + marshalizer := marshalizer + hasher := hasher + pubKeyConv := &mock.PubKeyConverterMock{} + tp, _ := process.NewTransactionProcessor(&mock.ProcessorStub{}, pubKeyConv, hasher, marshalizer) + + txHash, err := tp.ComputeTransactionHash(&data.Transaction{ + Nonce: protoTx.Nonce, + Value: protoTx.Value.String(), + Receiver: pubKeyConv.Encode(protoTx.RcvAddr), + Sender: pubKeyConv.Encode(protoTx.SndAddr), + GasPrice: protoTx.GasPrice, + GasLimit: protoTx.GasLimit, + Data: protoTx.Data, + Signature: hex.EncodeToString(protoTx.Signature), + ChainID: string(protoTx.ChainID), + Version: protoTx.Version, + }) + assert.Nil(t, err) + assert.Equal(t, protoTxHash, txHash) +} diff --git a/rosetta/Dockerfile b/rosetta/Dockerfile new file mode 100644 index 00000000..ef63e9bc --- /dev/null +++ b/rosetta/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.13.6 as builder + +WORKDIR /elrond +COPY . . +# Proxy +WORKDIR /elrond/cmd/proxy +RUN go build + +# ===== SECOND STAGE ====== +FROM ubuntu:18.04 +COPY --from=builder /elrond/cmd/proxy /elrond/cmd/proxy +# COPY config.toml file from rosetta folder to proxy config folder +COPY --from=builder /elrond/rosetta/config.toml /elrond/cmd/proxy/config/ + + +WORKDIR /elrond/cmd/proxy/ +EXPOSE 8079 +ENTRYPOINT ["./proxy", "--rosetta"] diff --git a/rosetta/Makefile b/rosetta/Makefile new file mode 100644 index 00000000..9d7346e7 --- /dev/null +++ b/rosetta/Makefile @@ -0,0 +1,24 @@ +GO_PACKAGES=./services/... ./provider/... +TEST_SCRIPT=go test ${GO_PACKAGES} +CURRENT_DIRECTORY=$(shell pwd) +LOCAL_DOCKER_TAG="rosetta-proxy-local:v1.0.0" + +build-docker-image: + cd ${CURRENT_DIRECTORY}/..; \ + docker image build . -t elrondnetwork/${LOCAL_DOCKER_TAG} -f ./rosetta/Dockerfile; \ + cd ${CURRENT_DIRECTORY} + echo ${LOCAL_DOCKER_TAG} > ~/.proxyDockerTag +run-mainnet: + ./rosetta.sh mainnet + +run-testnet: + ./rosetta.sh testnet + +stop: + ./rosetta.sh stop + +test: + ${TEST_SCRIPT} + +coverage-local: + ${TEST_SCRIPT} -cover diff --git a/rosetta/README.md b/rosetta/README.md new file mode 100644 index 00000000..441e3518 --- /dev/null +++ b/rosetta/README.md @@ -0,0 +1,82 @@ +## Overview + +This is the reference implementation of the [Rosetta API](https://rosetta-api.org) for Elrond, as an extension of the [Elrond Proxy](https://github.com/ElrondNetwork/elrond-proxy-go). + +The implementation is supported by an [Observing Squad](https://docs.elrond.com/observing-squad), where the Proxy starts as a gateway that resolves the impedance mismatch between the Elrond API (exposed by the Observer Nodes) and the Rosetta API. + +Note: An **Observing Squad** is defined as a set of `N` Observer Nodes (one for each Shard, including the Metachain) plus the Elrond Proxy instance (which connects to these Observers and delegates requests towards them). Currently the Elrond Mainnet has 3 Shards, plus the Metachain. Therefore, the Observing Squad is composed of 4 Observers and one Proxy instance. + + +One can set up a Rosetta-enabled Elrond Observing Squad via the provided scripts and Makefile - which use `docker` and `docker-compose` under the hood. + +## Features + +* Rosetta API implementation (both Data API and Construction API) +* Stateless, offline, curve-based transaction construction from any Bech32 Address + +## Prerequisites + +You need to install Docker and Docker Compose. + +For example, on Ubuntu: + +``` +sudo apt-install docker +sudo apt-install docker-compose +``` + +## Build + +In order to build the docker local image for the Proxy, run the following command +(if you don't want to build a local docker image, an image of proxy will be used from docker hub): + +``` +make build-docker-image +``` + +Under the hood, this command runs `docker build` against the Dockerfile `elrond-proxy`. + + +## Start + +Running the commands below will start a Rosetta-enabled Observing Squad (4 observer nodes, plus the Proxy). + +The API will be available at the following address: `http://10.0.0.2:8079`. + +### Testnet + +``` +make run-testnet +``` + +### Mainnet + +``` +make run-mainnet +``` + +## Stop + +In order to stop the Observing Squad, run the command: + +``` +make stop +``` + +## System Requirements + +The system requirements for an Observing Squad are listed [here](https://docs.elrond.com/observing-squad#system-requirements). + +## Testing with `rosetta-cli` + +In order to validate the Elrond implementation of the Rosetta API, [install `rosetta-cli`](https://github.com/coinbase/rosetta-cli#install) and run one of the following commands: + +* `rosetta-cli check:data --configuration-file rosetta-cli-conf/elrond_testnet.json` +* `rosetta-cli check:construction --configuration-file rosetta-cli-conf/elrond_testnet.json` +* `rosetta-cli check:data --configuration-file rosetta-cli-conf/elrond_mainnet.json` +* `rosetta-cli check:construction --configuration-file rosetta-cli-conf/elrond_mainnet.json` + +## Future Work + +* [Rosetta API `/mempool`](https://www.rosetta-api.org/docs/MempoolApi.html) +* [Rosetta API `/mempool/transaction`](https://www.rosetta-api.org/docs/MempoolApi.html#mempooltransaction) diff --git a/rosetta/api.go b/rosetta/api.go new file mode 100644 index 00000000..7385a9bd --- /dev/null +++ b/rosetta/api.go @@ -0,0 +1,102 @@ +package rosetta + +import ( + "fmt" + "net/http" + + logger "github.com/ElrondNetwork/elrond-go-logger" + "github.com/ElrondNetwork/elrond-proxy-go/api" + "github.com/ElrondNetwork/elrond-proxy-go/config" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/services" + "github.com/coinbase/rosetta-sdk-go/asserter" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +var log = logger.GetOrCreate("rosetta") + +// CreateServer creates a HTTP server +func CreateServer(elrondFacade api.ElrondProxyHandler, generalConfig *config.Config, port int) (*http.Server, error) { + elrondProvider, err := provider.NewElrondProvider(elrondFacade) + if err != nil { + log.Error("cannot create elrond provider", "err", err) + return nil, err + } + + networkConfig, err := elrondProvider.GetNetworkConfig() + if err != nil { + log.Error("cannot get network config", "err", err) + return nil, err + } + + cfg := configuration.LoadConfiguration(networkConfig, generalConfig) + + // The asserter automatically rejects incorrectly formatted + // requests. + asserterServer, err := asserter.NewServer( + services.SupportedOperationTypes, + false, + []*types.NetworkIdentifier{ + cfg.Network, + }, + ) + if err != nil { + log.Error("cannot create asserter", "err", err) + return nil, err + } + + // Create network service + networkAPIService := services.NewNetworkAPIService(elrondProvider, cfg) + networkAPIController := server.NewNetworkAPIController( + networkAPIService, + asserterServer, + ) + + // Create account service + accountAPIService := services.NewAccountAPIService(elrondProvider, cfg) + accountAPIController := server.NewAccountAPIController( + accountAPIService, + asserterServer, + ) + + // Create block service + blockAPIService := services.NewBlockAPIService(elrondProvider, cfg, networkConfig) + blockAPIController := server.NewBlockAPIController( + blockAPIService, + asserterServer, + ) + + // Create construction service + constructionAPIService := services.NewConstructionAPIService(elrondProvider, cfg, networkConfig) + constructionAPIController := server.NewConstructionAPIController( + constructionAPIService, + asserterServer, + ) + + // Create mempool service + mempoolAPIService := services.NewMempoolApiService(elrondProvider, cfg, networkConfig) + mempoolAPIController := server.NewMempoolAPIController( + mempoolAPIService, + asserterServer, + ) + + router := server.NewRouter( + networkAPIController, + accountAPIController, + blockAPIController, + constructionAPIController, + mempoolAPIController, + ) + + loggedRouter := server.LoggerMiddleware(router) + corsRouter := server.CorsMiddleware(loggedRouter) + + httpServer := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: corsRouter, + } + + return httpServer, nil +} diff --git a/rosetta/config.toml b/rosetta/config.toml new file mode 100644 index 00000000..f5eb72bc --- /dev/null +++ b/rosetta/config.toml @@ -0,0 +1,58 @@ +# GeneralSettings section of the proxy server +[GeneralSettings] + # ServerPort is the port used for the web server. The frontend will connect to this port + ServerPort = 8079 + + # RequestTimeoutSec represents the maximum number of seconds a request can last until throwing an error + RequestTimeoutSec = 10 + + # HeartbeatCacheValidityDurationSec represents the maximum number of seconds the heartbeat cache data is valid before it + # should be updated + HeartbeatCacheValidityDurationSec = 25 + + # ValStatsCacheValidityDurationSec represents the maximum number of seconds the validator statistics cache data is valid + # before it should be updated + ValStatsCacheValidityDurationSec = 60 + + # BalancedObservers - if this flag is set to true, then the requests will be distributed equally between observers. + # Otherwise, there are chances that only one observer from a shard will process the requests + BalancedObservers = true + + # BalancedFullHistoryNodes - if this flag is set to true, then the requests will be distributed equally between full history nodes. + # Otherwise, there are chances that only one full history node from a shard will process the requests + BalancedFullHistoryNodes = true + + # FaucetValue represents the default value for a faucet transaction. If set to "0", the faucet feature will be disabled + FaucetValue = "0" + +[AddressPubkeyConverter] + #Length specifies the length in bytes of an address + Length = 32 + + # Type specifies the type of public keys: hex or bech32 + Type = "bech32" + +[Marshalizer] + Type = "gogo protobuf" + +[Hasher] + Type = "blake2b" + + +# List of Observers. If you want to define a metachain observer (needed for validator statistics route) use +# shard id 4294967295 + [[Observers]] + ShardId = 0 + Address = "http://10.0.0.6:8080" + + [[Observers]] + ShardId = 1 + Address = "http://10.0.0.5:8080" + + [[Observers]] + ShardId = 2 + Address = "http://10.0.0.4:8080" + + [[Observers]] + ShardId = 4294967295 + Address = "http://10.0.0.3:8080" diff --git a/rosetta/configuration/configuration.go b/rosetta/configuration/configuration.go new file mode 100644 index 00000000..f8a7c66e --- /dev/null +++ b/rosetta/configuration/configuration.go @@ -0,0 +1,81 @@ +package configuration + +import ( + "encoding/hex" + + "github.com/ElrondNetwork/elrond-proxy-go/config" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" +) + +const ( + BlockchainName = "Elrond" + MainnetChainID = "1" + + MainnetElrondSymbol = "eGLD" + TestnetElrondSymbol = "XeGLD" + NumDecimals = 18 + + // GenesisBlockHash is const that will keep genesis block hash in hex format + GenesisBlockHashMainnet = "cd229e4ad2753708e4bab01d7f249affe29441829524c9529e84d51b6d12f2a7" + TestnetGenesisBlock = "0000000000000000000000000000000000000000000000000000000000000000" +) + +// Configuration is structure used for rosetta provider configuration +type Configuration struct { + Network *types.NetworkIdentifier + Currency *types.Currency + GenesisBlockIdentifier *types.BlockIdentifier + Peers []*types.Peer +} + +//LoadConfiguration will load configuration +func LoadConfiguration(networkConfig *provider.NetworkConfig, generalConfig *config.Config) *Configuration { + peers := make([]*types.Peer, len(generalConfig.Observers)) + for idx, observer := range generalConfig.Observers { + peer := &types.Peer{ + PeerID: hex.EncodeToString([]byte(observer.Address)), + Metadata: map[string]interface{}{ + "address": observer.Address, + "shardID": observer.ShardId, + }, + } + peers[idx] = peer + } + + switch networkConfig.ChainID { + case MainnetChainID: + return &Configuration{ + Network: &types.NetworkIdentifier{ + Blockchain: BlockchainName, + Network: networkConfig.ChainID, + }, + Currency: &types.Currency{ + Symbol: MainnetElrondSymbol, + Decimals: NumDecimals, + }, + GenesisBlockIdentifier: &types.BlockIdentifier{ + Index: 1, + Hash: GenesisBlockHashMainnet, + }, + Peers: peers, + } + default: + // other testnets + return &Configuration{ + Network: &types.NetworkIdentifier{ + Blockchain: BlockchainName, + Network: networkConfig.ChainID, + }, + Currency: &types.Currency{ + Symbol: TestnetElrondSymbol, + Decimals: NumDecimals, + }, + GenesisBlockIdentifier: &types.BlockIdentifier{ + Index: 1, + Hash: TestnetGenesisBlock, + }, + Peers: peers, + } + } +} diff --git a/rosetta/mocks/elrond_provider_mock.go b/rosetta/mocks/elrond_provider_mock.go new file mode 100644 index 00000000..8ea20b97 --- /dev/null +++ b/rosetta/mocks/elrond_provider_mock.go @@ -0,0 +1,104 @@ +package mocks + +import ( + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" +) + +// ElrondProviderMock - +type ElrondProviderMock struct { + GetNetworkConfigCalled func() (*provider.NetworkConfig, error) + GetLatestBlockDataCalled func() (*provider.BlockData, error) + GetBlockByNonceCalled func(nonce int64) (*data.Hyperblock, error) + GetBlockByHashCalled func(hash string) (*data.Hyperblock, error) + GetAccountCalled func(address string) (*data.Account, error) + EncodeAddressCalled func(address []byte) (string, error) + SendTxCalled func(tx *data.Transaction) (string, error) + ComputeTransactionHashCalled func(tx *data.Transaction) (string, error) + CalculateBlockTimestampUnixCalled func(round uint64) int64 + GetTransactionByHashFromPoolCalled func(txHash string) (*data.FullTransaction, bool) + DecodeAddressCalled func(address string) ([]byte, error) +} + +// GetNetworkConfig - +func (epm *ElrondProviderMock) GetNetworkConfig() (*provider.NetworkConfig, error) { + if epm.GetNetworkConfigCalled != nil { + return epm.GetNetworkConfigCalled() + } + return nil, nil +} + +// GetLatestBlockData - +func (epm *ElrondProviderMock) GetLatestBlockData() (*provider.BlockData, error) { + if epm.GetLatestBlockDataCalled != nil { + return epm.GetLatestBlockDataCalled() + } + + return nil, nil +} + +// GetBlockByNonce - +func (epm *ElrondProviderMock) GetBlockByNonce(nonce int64) (*data.Hyperblock, error) { + if epm.GetBlockByNonceCalled != nil { + return epm.GetBlockByNonceCalled(nonce) + } + return nil, nil +} + +// GetBlockByHash - +func (epm *ElrondProviderMock) GetBlockByHash(_ string) (*data.Hyperblock, error) { + return nil, nil +} + +// GetAccount - +func (epm *ElrondProviderMock) GetAccount(address string) (*data.Account, error) { + if epm.GetAccountCalled != nil { + return epm.GetAccountCalled(address) + } + return nil, nil +} + +// EncodeAddress - +func (epm *ElrondProviderMock) EncodeAddress(pubkey []byte) (string, error) { + if epm.EncodeAddressCalled != nil { + return epm.EncodeAddressCalled(pubkey) + } + return "", nil +} + +// SendTx - +func (epm *ElrondProviderMock) SendTx(tx *data.Transaction) (string, error) { + if epm.SendTxCalled != nil { + return epm.SendTxCalled(tx) + } + return "", nil +} + +// ComputeTransactionHash - +func (epm *ElrondProviderMock) ComputeTransactionHash(tx *data.Transaction) (string, error) { + if epm.ComputeTransactionHashCalled != nil { + return epm.ComputeTransactionHashCalled(tx) + } + return "", nil +} + +// CalculateBlockTimestampUnix - +func (epm *ElrondProviderMock) CalculateBlockTimestampUnix(_ uint64) int64 { + return 0 +} + +// GetTransactionByHashFromPool - +func (epm *ElrondProviderMock) GetTransactionByHashFromPool(txHash string) (*data.FullTransaction, bool) { + if epm.GetTransactionByHashFromPoolCalled != nil { + return epm.GetTransactionByHashFromPoolCalled(txHash) + } + return nil, false +} + +// DecodeAddress - +func (epm *ElrondProviderMock) DecodeAddress(address string) ([]byte, error) { + if epm.DecodeAddressCalled != nil { + return epm.DecodeAddressCalled(address) + } + return nil, nil +} diff --git a/rosetta/playground/account.http b/rosetta/playground/account.http new file mode 100644 index 00000000..ef82c449 --- /dev/null +++ b/rosetta/playground/account.http @@ -0,0 +1,16 @@ +@baseUrl = https://rosetta-api-beta.elrond.com +@baseNetwork = 1 + +### ACCOUNT BALANCE +POST {{baseUrl}}/account/balance HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "account_identifier": { + "address": "erd1qdzvfpa7gqjsnfhdxhvcp2mlysc80uz60yjhxre3lwl00q0jd4nqgauy9q" + } +} diff --git a/rosetta/playground/block.http b/rosetta/playground/block.http new file mode 100644 index 00000000..47f66b6c --- /dev/null +++ b/rosetta/playground/block.http @@ -0,0 +1,17 @@ +@baseUrl = https://rosetta-api-beta.elrond.com +@baseNetwork = 1 + + +### BLOCK BY NONCE +POST {{baseUrl}}/block HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "block_identifier": { + "index": 143658 + } +} diff --git a/rosetta/playground/construction.http b/rosetta/playground/construction.http new file mode 100644 index 00000000..60272c08 --- /dev/null +++ b/rosetta/playground/construction.http @@ -0,0 +1,216 @@ +@baseUrl = https://rosetta-api-beta.elrond.com +@baseNetwork = 1 + + +## CONSTRUCTION PREPROCESS +POST {{baseUrl}}/construction/preprocess HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "operations": [ + { + "operation_identifier": { + "index": 0 + }, + "type": "Transfer", + "account": { + "address": "erd1qqqqqqqqqqqqqpgqufjsm9cumjc8kj0rhcxzh90y62c7h66063ssz394p9" + }, + "amount": { + "value": "-60457060312024360732", + "currency": { + "symbol": "eGLD", + "decimals": 18 + } + } + }, + { + "operation_identifier": { + "index": 1 + }, + "related_operations": [ + { + "index": 0 + } + ], + "type": "Transfer", + "account": { + "address": "erd1qrppu0ezzd6ws4nwxgudwzh7p59c8dzhl46ypstwnfrntltlkaeq48nurl" + }, + "amount": { + "value": "60457060312024360732", + "currency": { + "symbol": "eGLD", + "decimals": 18 + } + } + } + ], + "metadata": { + "gasPrice": 10000, + "gasLimit": 1, + "data": "hello" + }, + "max_fee": [ + { + "value": "2000000000", + "currency": { + "symbol": "eGLD", + "decimals": 18 + }, + "metadata": { + + } + } + ], + "suggested_fee_multiplier": 1.1 +} + +### CONSTRUCTION PREPROCESS +POST {{baseUrl}}/construction/metadata HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "options": { + "feeMultiplier": 1.1, + "maxFee": "2000000000", + "receiver": "erd1qrppu0ezzd6ws4nwxgudwzh7p59c8dzhl46ypstwnfrntltlkaeq48nurl", + "sender": "erd1y9e2nxq0tfc0qunq0q2ysqs5tlffwndq86e9y49q5y8yarf3dhsqkdkz6q", + "type": "Transfer", + "value": "60457060312024360732", + "gasPrice": 1000000000, + "gasLimit": 58000, + "data": "hello" + } +} + +### CONSTRUCTION PREPROCESS +POST {{baseUrl}}/construction/payloads HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "operations": [ + { + "operation_identifier": { + "index": 0 + }, + "type": "Transfer", + "account": { + "address": "erd1y9e2nxq0tfc0qunq0q2ysqs5tlffwndq86e9y49q5y8yarf3dhsqkdkz6q" + }, + "amount": { + "value": "-60457060312024360732", + "currency": { + "symbol": "eGLD", + "decimals": 18 + } + } + }, + { + "operation_identifier": { + "index": 1 + }, + "related_operations": [ + { + "index": 0 + } + ], + "type": "Transfer", + "account": { + "address": "erd1qrppu0ezzd6ws4nwxgudwzh7p59c8dzhl46ypstwnfrntltlkaeq48nurl" + }, + "amount": { + "value": "60457060312024360732", + "currency": { + "symbol": "eGLD", + "decimals": 18 + } + } + } + ], + "metadata": { + "chainID": "local-testnet", + "gasLimit": 58000, + "gasPrice": 1000000000, + "nonce": 0, + "receiver": "erd1qrppu0ezzd6ws4nwxgudwzh7p59c8dzhl46ypstwnfrntltlkaeq48nurl", + "sender": "erd1y9e2nxq0tfc0qunq0q2ysqs5tlffwndq86e9y49q5y8yarf3dhsqkdkz6q", + "value": "60457060312024360732", + "version": 1, + "data": "aGVsbG8=" + } +} + +### CONSTRUCTION DERIVE +POST {{baseUrl}}/construction/derive HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "public_key": { + "hex_bytes": "7c3f38ab6d2f961de7e5ad914cdbd0b6361b5ddb53d504b5297bfa4c901fc1d8", + "curve_type": "edwards25519" + } +} + +### CONSTRUCTION HASH +POST {{baseUrl}}/construction/hash HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "signed_transaction":"7b226e6f6e6365223a302c2276616c7565223a22393030313136303837313033353636313638222c227265636569766572223a226572643172763971716e306e3535773074333578647872776c6874787a39656e797833647a3067726870776e616c77356c73753032796671396134357a68222c2273656e646572223a226572643179726567777068377461766c336e32657036337471633333633864756a39757271376e6c67673278766c356b373463396b61707367637a75746c222c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c227369676e6174757265223a226163373731333832383762346432313764316464613664303365356263623436363338356538656531646335343464356635303836363430333335373933656630386538613436323536653166343038626236633138353730366236633231626539643139663338666561323866393239376161643964636435313839383033222c22636861696e4944223a2254222c2276657273696f6e223a317d" +} + +### CONSTRUCTION HASH +POST {{baseUrl}}/construction/submit HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "signed_transaction":"7b226e6f6e6365223a302c2276616c7565223a22393030313136303837313033353636313638222c227265636569766572223a226572643172763971716e306e3535773074333578647872776c6874787a39656e797833647a3067726870776e616c77356c73753032796671396134357a68222c2273656e646572223a226572643179726567777068377461766c336e32657036337471633333633864756a39757271376e6c67673278766c356b373463396b61707367637a75746c222c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c227369676e6174757265223a226163373731333832383762346432313764316464613664303365356263623436363338356538656531646335343464356635303836363430333335373933656630386538613436323536653166343038626236633138353730366236633231626539643139663338666561323866393239376161643964636435313839383033222c22636861696e4944223a2254222c2276657273696f6e223a317d" +} + +### CONSTRUCTION HASH +POST {{baseUrl}}/construction/combine HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier":{ + "blockchain":"Elrond", + "network":"{{baseNetwork}}" + }, + "unsigned_transaction":"7b226e6f6e6365223a302c2276616c7565223a22383139353830353433343637363832323035222c227265636569766572223a2265726431736a7a756e6161716a756e7432396a64326d6177776d6d356666717675327a3866796872613034657976666634663861357339736b32346d3030222c2273656e646572223a22657264317436746d3877657a387438373867326a687738386c6d70336a7a67396e6a6730776b366b6c6c7a776d7574667477347432636473383833736670222c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c22636861696e4944223a2254222c2276657273696f6e223a317d","signatures":[{"hex_bytes":"0f63d18f9256ec4eb161cbcc30daaa48a7c685d315f3848f14cea3e5beaf795cc0231f73c81ef76fde95bbb4b23298d651b716409ea979867be1a939bded5007", + "signing_payload":{ + "address":"erd1t6tm8wez8t878g2jhw88lmp3jzg9njg0wk6kllzwmutftw4t2cds883sfp", + "hex_bytes":"7b226e6f6e6365223a302c2276616c7565223a22383139353830353433343637363832323035222c227265636569766572223a2265726431736a7a756e6161716a756e7432396a64326d6177776d6d356666717675327a3866796872613034657976666634663861357339736b32346d3030222c2273656e646572223a22657264317436746d3877657a387438373867326a687738386c6d70336a7a67396e6a6730776b366b6c6c7a776d7574667477347432636473383833736670222c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c22636861696e4944223a2254222c2276657273696f6e223a317d", + "account_identifier":{ + "address":"erd1t6tm8wez8t878g2jhw88lmp3jzg9njg0wk6kllzwmutftw4t2cds883sfp"}, + "signature_type":"ed25519"}, + "public_key":{ + "hex_bytes":"5e97b3bb223acfe3a152bb8e7fec31909059c90f75b56ffc4edf1695baab561b", + "curve_type":"edwards25519"}, + "signature_type":"ed25519"} + ] +} diff --git a/rosetta/playground/http-client.env.json b/rosetta/playground/http-client.env.json new file mode 100644 index 00000000..334eff6a --- /dev/null +++ b/rosetta/playground/http-client.env.json @@ -0,0 +1,6 @@ +{ + "dev": { + "baseUrl": "https://rosetta-api-beta.elrond.com", + "baseNetwork": "1" + } +} diff --git a/rosetta/playground/mempool.http b/rosetta/playground/mempool.http new file mode 100644 index 00000000..6bb59923 --- /dev/null +++ b/rosetta/playground/mempool.http @@ -0,0 +1,15 @@ +@baseUrl = https://rosetta-api-beta.elrond.com +@baseNetwork = 1 + +POST {{baseUrl}}/mempool/transaction HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "transaction_identifier": { + "hash": "573c18c81eb970b4cfe7f6fc515d772646c5aac8e4fea2452a44d67fbf47efe9" + } +} diff --git a/rosetta/playground/network.http b/rosetta/playground/network.http new file mode 100644 index 00000000..80d08568 --- /dev/null +++ b/rosetta/playground/network.http @@ -0,0 +1,35 @@ +@baseUrl = https://rosetta-api-beta.elrond.com +@baseNetwork = 1 + + +### NETWORK LIST +POST {{baseUrl}}/network/list HTTP/1.1 +Content-Type: application/json + +{ +} + +### NETWORK OPTIONS +POST {{baseUrl}}/network/options HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "metadata": {} +} + + +### NETWORK STATUS +POST {{baseUrl}}/network/status HTTP/1.1 +Content-Type: application/json + +{ + "network_identifier": { + "blockchain": "Elrond", + "network": "{{baseNetwork}}" + }, + "metadata": {} +} \ No newline at end of file diff --git a/rosetta/provider/common.go b/rosetta/provider/common.go new file mode 100644 index 00000000..e7c901e4 --- /dev/null +++ b/rosetta/provider/common.go @@ -0,0 +1,26 @@ +package provider + +const ( + MetachainID = 4294967295 +) + +// NetworkConfig is the struct used to store network config information +type NetworkConfig struct { + ChainID string `json:"erd_chain_id"` + Denomination uint64 `json:"erd_denomination"` + GasPerDataByte uint64 `json:"erd_gas_per_data_byte"` + ClientVersion string `json:"erd_latest_tag_software_version"` + MinGasPrice uint64 `json:"erd_min_gas_price"` + MinGasLimit uint64 `json:"erd_min_gas_limit"` + MinTxVersion uint32 `json:"erd_min_transaction_version"` + StartTime uint64 `json:"erd_start_time"` + RoundDuration uint64 `json:"erd_round_duration"` +} + +// BlockData is the struct used to store information about a block +type BlockData struct { + Nonce uint64 + Hash string + PrevBlockHash string + Timestamp int64 +} diff --git a/rosetta/provider/elrond_provider.go b/rosetta/provider/elrond_provider.go new file mode 100644 index 00000000..e15ee9d4 --- /dev/null +++ b/rosetta/provider/elrond_provider.go @@ -0,0 +1,255 @@ +package provider + +import ( + "encoding/json" + "errors" + "time" + + logger "github.com/ElrondNetwork/elrond-go-logger" + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-proxy-go/api" + "github.com/ElrondNetwork/elrond-proxy-go/data" +) + +// ElrondProvider is able to process requests +type ElrondProvider struct { + client ElrondProxyClient + genesisTime uint64 + roundDurationMilliseconds uint64 +} + +const ( + MaxRetriesGetNetworkConfig = 20 + DelayBetweenRetries = 5 * time.Second +) + +var ( + log = logger.GetOrCreate("rosetta/provider") + // ErrInvalidElrondProxyHandler signals that provided elrond proxy handler is not a elrond proxy provider + ErrInvalidElrondProxyHandler = errors.New("invalid elrond proxy handler") +) + +//NewElrondProvider will create a new instance of ElrondProvider +func NewElrondProvider(elrondFacade api.ElrondProxyHandler) (*ElrondProvider, error) { + elrondProxy, ok := elrondFacade.(ElrondProxyClient) + if !ok { + return nil, ErrInvalidElrondProxyHandler + } + + elrondProvider := &ElrondProvider{ + client: elrondProxy, + } + + err := elrondProvider.initializeElrondProvider() + if err != nil { + return nil, err + } + + return elrondProvider, nil +} + +func (ep *ElrondProvider) initializeElrondProvider() error { + var err error + + networkConfig := &NetworkConfig{} + for count := 0; count < MaxRetriesGetNetworkConfig; count++ { + networkConfig, err = ep.GetNetworkConfig() + if err != nil { + time.Sleep(DelayBetweenRetries) + continue + } + + break + } + // if maxRetries is reached we should return error here because we did maxRetries to get network config + // but the observers did not answer + if err != nil { + return err + } + + ep.genesisTime = networkConfig.StartTime + ep.roundDurationMilliseconds = networkConfig.RoundDuration + + return nil +} + +// GetNetworkConfig will return the network config +func (ep *ElrondProvider) GetNetworkConfig() (*NetworkConfig, error) { + networkConfigResponse, err := ep.client.GetNetworkConfigMetrics() + if err != nil { + log.Warn("cannot get network metrics", "error", err.Error()) + + return nil, err + } + + if networkConfigResponse.Error != "" { + log.Warn("cannot get network metrics", "error", networkConfigResponse.Error) + + return nil, errors.New(networkConfigResponse.Error) + } + + networkConfig := &NetworkConfig{} + + responseDataI, ok := networkConfigResponse.Data.(map[string]interface{}) + if !ok { + return nil, errors.New("response data is invalid") + } + responseData, ok := responseDataI["config"] + if !ok { + return nil, errors.New("response data is invalid network config is not in response") + } + + responseBytes, err := json.Marshal(responseData) + if err != nil { + log.Warn("cannot marshal network config response", "error", err.Error()) + + return nil, err + } + + err = json.Unmarshal(responseBytes, networkConfig) + + return networkConfig, err +} + +// GetLatestBlockData will return latest block data +func (ep *ElrondProvider) GetLatestBlockData() (*BlockData, error) { + latestBlockNonce, err := ep.client.GetLatestFullySynchronizedHyperblockNonce() + if err != nil { + return nil, err + } + + blockResponse, err := ep.client.GetBlockByNonce(MetachainID, latestBlockNonce, false) + if err != nil { + log.Warn("cannot get block", "nonce", latestBlockNonce, + "error", err.Error()) + + return nil, err + } + + if blockResponse.Error != "" { + log.Warn("cannot get block", "nonce", latestBlockNonce, + "error", blockResponse.Error) + + return nil, err + } + + return &BlockData{ + Nonce: blockResponse.Data.Block.Nonce, + Hash: blockResponse.Data.Block.Hash, + PrevBlockHash: blockResponse.Data.Block.PrevBlockHash, + Timestamp: ep.CalculateBlockTimestampUnix(blockResponse.Data.Block.Round), + }, nil +} + +// GetBlockByNonce will return a block by nonce +func (ep *ElrondProvider) GetBlockByNonce(nonce int64) (*data.Hyperblock, error) { + blockResponse, err := ep.client.GetHyperBlockByNonce(uint64(nonce)) + if err != nil { + log.Warn("cannot get hyper block", "nonce", nonce, + "error", err.Error()) + + return nil, err + } + + if blockResponse.Error != "" { + log.Warn("cannot get hyper block", "nonce", nonce, + "error", blockResponse.Error) + + return nil, errors.New(blockResponse.Error) + } + + return &blockResponse.Data.Hyperblock, nil +} + +// GetBlockByHash will return a hyper block by hash +func (ep *ElrondProvider) GetBlockByHash(hash string) (*data.Hyperblock, error) { + blockResponse, err := ep.client.GetHyperBlockByHash(hash) + if err != nil { + log.Warn("cannot get hyper block", "hash", hash, + "error", err.Error()) + + return nil, err + } + + if blockResponse.Error != "" { + log.Warn("cannot get hyper block", "hash", hash, + "error", blockResponse.Error) + + return nil, errors.New(blockResponse.Error) + } + + return &blockResponse.Data.Hyperblock, nil +} + +// GetAccount will return an account by address +func (ep *ElrondProvider) GetAccount(address string) (*data.Account, error) { + return ep.client.GetAccount(address) +} + +// ComputeTransactionHash will compute hash of provided transaction +func (ep *ElrondProvider) ComputeTransactionHash(tx *data.Transaction) (string, error) { + return ep.client.ComputeTransactionHash(tx) +} + +// EncodeAddress will encode an address +func (ep *ElrondProvider) EncodeAddress(address []byte) (string, error) { + pubKeyConverter, err := ep.client.GetAddressConverter() + if err != nil { + return "", err + } + + return pubKeyConverter.Encode(address), nil +} + +// DecodeAddress will decode an address +func (ep *ElrondProvider) DecodeAddress(address string) ([]byte, error) { + pubKeyConverter, err := ep.client.GetAddressConverter() + if err != nil { + return nil, err + } + + return pubKeyConverter.Decode(address) +} + +// SendTx will send a transaction +func (ep *ElrondProvider) SendTx(tx *data.Transaction) (string, error) { + _, hash, err := ep.client.SendTransaction(tx) + if err != nil { + return "", err + } + + return hash, nil +} + +// CalculateBlockTimestampUnix will calculate block timestamp +func (ep *ElrondProvider) CalculateBlockTimestampUnix(round uint64) int64 { + startTimeMilliseconds := ep.genesisTime * 1000 + + return int64(startTimeMilliseconds) + int64(round*ep.roundDurationMilliseconds) +} + +// GetTransactionByHashFromPool will return a transaction only if is in pool +func (ep *ElrondProvider) GetTransactionByHashFromPool(txHash string) (*data.FullTransaction, bool) { + tx, _, err := ep.client.GetTransactionByHashAndSenderAddress(txHash, "") + if err != nil { + log.Debug("elrond provider: cannot get transaction by hash", "error", err.Error()) + return nil, false + } + + if !isTxFromPool(tx) { + return nil, false + } + + return tx, true +} + +func isTxFromPool(tx *data.FullTransaction) bool { + acceptedTxStatuses := []transaction.TxStatus{transaction.TxStatusReceived, transaction.TxStatusPartiallyExecuted} + for idx := 0; idx < len(acceptedTxStatuses); idx++ { + if acceptedTxStatuses[idx] == tx.Status { + return true + } + } + + return false +} diff --git a/rosetta/provider/elrond_provider_test.go b/rosetta/provider/elrond_provider_test.go new file mode 100644 index 00000000..75555f87 --- /dev/null +++ b/rosetta/provider/elrond_provider_test.go @@ -0,0 +1,365 @@ +package provider + +import ( + "encoding/hex" + "errors" + "testing" + + "github.com/ElrondNetwork/elrond-go/config" + "github.com/ElrondNetwork/elrond-go/core" + "github.com/ElrondNetwork/elrond-go/data/state/factory" + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider/mock" + "github.com/stretchr/testify/assert" +) + +func TestInitializeElrondProvider(t *testing.T) { + t.Parallel() + + localErr := errors.New("err") + count := 0 + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxy := &mock.ElrondProxyClientMock{} + elrondProxy.GetNetworkConfigMetricsCalled = func() (*data.GenericAPIResponse, error) { + if count == 2 { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + } + count++ + return nil, localErr + } + + elrondProxyClient, err := NewElrondProvider(elrondProxy) + assert.Nil(t, err) + assert.Equal(t, roundDuration, elrondProxyClient.roundDurationMilliseconds) + assert.Equal(t, startTime, elrondProxyClient.genesisTime) +} + +func TestNewElrondProvider_InvalidHandlerShouldErr(t *testing.T) { + t.Parallel() + + elrondProvider, err := NewElrondProvider(nil) + + assert.Nil(t, elrondProvider) + assert.Equal(t, ErrInvalidElrondProxyHandler, err) +} + +func TestElrondProvider_GetLatestBlockData(t *testing.T) { + t.Parallel() + + blockNonce := uint64(10) + blockHash := "hash" + preBlockHash := "prevBlockHash" + round := uint64(11) + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetLatestFullySynchronizedHyperblockNonceCalled: func() (uint64, error) { + return blockNonce, nil + }, + GetBlockByNonceCalled: func(shardID uint32, nonce uint64, withTxs bool) (*data.BlockApiResponse, error) { + return &data.BlockApiResponse{ + Data: data.BlockApiResponsePayload{ + Block: data.Block{ + Nonce: blockNonce, + Round: round, + Hash: blockHash, + PrevBlockHash: preBlockHash, + }, + }, + }, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + blockData, err := elrondProvider.GetLatestBlockData() + assert.Nil(t, err) + assert.Equal(t, &BlockData{ + Nonce: blockNonce, + Hash: blockHash, + PrevBlockHash: preBlockHash, + Timestamp: 1044000, + }, blockData) +} + +func TestElrondProvider_GetBlockByNonce(t *testing.T) { + t.Parallel() + + blockNonce := uint64(10) + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetHyperBlockByNonceCalled: func(nonce uint64) (*data.HyperblockApiResponse, error) { + return &data.HyperblockApiResponse{ + Data: data.HyperblockApiResponsePayload{ + Hyperblock: data.Hyperblock{ + Nonce: blockNonce, + }, + }, + }, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + hyperBlock, err := elrondProvider.GetBlockByNonce(int64(blockNonce)) + assert.Nil(t, err) + assert.Equal(t, &data.Hyperblock{Nonce: blockNonce}, hyperBlock) +} + +func TestElrondProvider_GetBlockByHash(t *testing.T) { + t.Parallel() + + blockHash := "hash-hash" + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetHyperBlockByHashCalled: func(hash string) (*data.HyperblockApiResponse, error) { + return &data.HyperblockApiResponse{ + Data: data.HyperblockApiResponsePayload{ + Hyperblock: data.Hyperblock{ + Hash: blockHash, + }, + }, + }, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + hyperBlock, err := elrondProvider.GetBlockByHash(blockHash) + assert.Nil(t, err) + assert.Equal(t, &data.Hyperblock{Hash: blockHash}, hyperBlock) +} + +func TestElrondProvider_GetAccount(t *testing.T) { + t.Parallel() + + accountAddr := "addr-addr" + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetAccountCalled: func(address string) (*data.Account, error) { + return &data.Account{ + Address: accountAddr, + }, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + accountRet, err := elrondProvider.GetAccount(accountAddr) + assert.Nil(t, err) + assert.Equal(t, &data.Account{Address: accountAddr}, accountRet) +} + +func TestElrondProvider_ComputeTransactionHash(t *testing.T) { + t.Parallel() + + transactionHash := "hash-hash" + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + ComputeTransactionHashCalled: func(tx *data.Transaction) (string, error) { + return transactionHash, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + hash, err := elrondProvider.ComputeTransactionHash(&data.Transaction{}) + assert.Nil(t, err) + assert.Equal(t, transactionHash, hash) +} + +func TestElrondProvider_EncodeAddress(t *testing.T) { + t.Parallel() + + addrBytes, _ := hex.DecodeString("7c3f38ab6d2f961de7e5ad914cdbd0b6361b5ddb53d504b5297bfa4c901fc1d8") + expectedAddr := "erd10sln32md97tpmel94kg5ek7skcmpkhwm202sfdff00ayeyqlc8vqpajkz5" + pubKeyConverter, _ := factory.NewPubkeyConverter(config.PubkeyConfig{ + Length: 32, + Type: "bech32", + }) + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetAddressConverterCalled: func() (core.PubkeyConverter, error) { + return pubKeyConverter, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + bech32Addr, err := elrondProvider.EncodeAddress(addrBytes) + assert.Nil(t, err) + assert.Equal(t, expectedAddr, bech32Addr) +} + +func TestElrondProvider_SendTx(t *testing.T) { + t.Parallel() + + transactionHash := "hash-hash" + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + SendTransactionCalled: func(tx *data.Transaction) (int, string, error) { + return 0, transactionHash, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + hash, err := elrondProvider.SendTx(&data.Transaction{}) + assert.Nil(t, err) + assert.Equal(t, transactionHash, hash) +} + +func TestElrondProvider_GetTransactionByHashFromPool_TxNotInPool(t *testing.T) { + t.Parallel() + + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetTransactionByHashAndSenderAddressCalled: func(hash string, sndAddr string) (*data.FullTransaction, int, error) { + return &data.FullTransaction{ + Status: transaction.TxStatusExecuted, + }, 0, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + tx, isInPool := elrondProvider.GetTransactionByHashFromPool("hash") + assert.Nil(t, tx) + assert.False(t, isInPool) +} + +func TestElrondProvider_GetTransactionByHashFromPool_TxInPool(t *testing.T) { + t.Parallel() + + roundDuration := uint64(4000) + startTime := uint64(1000) + elrondProxyMock := &mock.ElrondProxyClientMock{ + GetNetworkConfigMetricsCalled: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + "config": map[string]interface{}{ + "erd_chain_id": "1", + "erd_round_duration": roundDuration, + "erd_start_time": startTime, + }, + }, + }, nil + }, + GetTransactionByHashAndSenderAddressCalled: func(hash string, sndAddr string) (*data.FullTransaction, int, error) { + return &data.FullTransaction{ + Status: transaction.TxStatusReceived, + }, 0, nil + }, + } + + elrondProvider, _ := NewElrondProvider(elrondProxyMock) + + tx, isInPool := elrondProvider.GetTransactionByHashFromPool("hash") + assert.Equal(t, &data.FullTransaction{Status: transaction.TxStatusReceived}, tx) + assert.True(t, isInPool) +} diff --git a/rosetta/provider/interface.go b/rosetta/provider/interface.go new file mode 100644 index 00000000..5575dff5 --- /dev/null +++ b/rosetta/provider/interface.go @@ -0,0 +1,38 @@ +package provider + +import ( + "github.com/ElrondNetwork/elrond-go/core" + "github.com/ElrondNetwork/elrond-proxy-go/data" +) + +// ElrondProxyClient defines what a real elrond proxy client should do +type ElrondProxyClient interface { + GetNetworkConfigMetrics() (*data.GenericAPIResponse, error) + GetBlockByNonce(shardID uint32, nonce uint64, withTxs bool) (*data.BlockApiResponse, error) + GetAccount(address string) (*data.Account, error) + + GetHyperBlockByNonce(nonce uint64) (*data.HyperblockApiResponse, error) + GetHyperBlockByHash(hash string) (*data.HyperblockApiResponse, error) + + SendTransaction(tx *data.Transaction) (int, string, error) + ComputeTransactionHash(tx *data.Transaction) (string, error) + GetTransactionByHashAndSenderAddress(txHash string, sndAddr string) (*data.FullTransaction, int, error) + + GetLatestFullySynchronizedHyperblockNonce() (uint64, error) + GetAddressConverter() (core.PubkeyConverter, error) +} + +// ElrondProviderHandler defines what a real elrond provider should do +type ElrondProviderHandler interface { + GetNetworkConfig() (*NetworkConfig, error) + GetLatestBlockData() (*BlockData, error) + GetBlockByNonce(nonce int64) (*data.Hyperblock, error) + GetBlockByHash(hash string) (*data.Hyperblock, error) + GetAccount(address string) (*data.Account, error) + EncodeAddress(address []byte) (string, error) + DecodeAddress(address string) ([]byte, error) + SendTx(tx *data.Transaction) (string, error) + CalculateBlockTimestampUnix(round uint64) int64 + ComputeTransactionHash(tx *data.Transaction) (string, error) + GetTransactionByHashFromPool(txHash string) (*data.FullTransaction, bool) +} diff --git a/rosetta/provider/mock/elrond_proxy_mock.go b/rosetta/provider/mock/elrond_proxy_mock.go new file mode 100644 index 00000000..e3aa8c22 --- /dev/null +++ b/rosetta/provider/mock/elrond_proxy_mock.go @@ -0,0 +1,106 @@ +package mock + +import ( + "github.com/ElrondNetwork/elrond-go/core" + "github.com/ElrondNetwork/elrond-proxy-go/data" +) + +// ElrondProxyClientMock - +type ElrondProxyClientMock struct { + GetNetworkConfigMetricsCalled func() (*data.GenericAPIResponse, error) + GetBlockByNonceCalled func(shardID uint32, nonce uint64, withTxs bool) (*data.BlockApiResponse, error) + GetAccountCalled func(address string) (*data.Account, error) + GetHyperBlockByNonceCalled func(nonce uint64) (*data.HyperblockApiResponse, error) + GetHyperBlockByHashCalled func(hash string) (*data.HyperblockApiResponse, error) + SendTransactionCalled func(tx *data.Transaction) (int, string, error) + SimulateTransactionCalled func(tx *data.Transaction) (*data.ResponseTransactionSimulation, error) + GetAddressConverterCalled func() (core.PubkeyConverter, error) + GetLatestFullySynchronizedHyperblockNonceCalled func() (uint64, error) + ComputeTransactionHashCalled func(tx *data.Transaction) (string, error) + GetTransactionByHashAndSenderAddressCalled func(hash string, sndAddr string) (*data.FullTransaction, int, error) +} + +// GetNetworkConfigMetrics - +func (epcm *ElrondProxyClientMock) GetNetworkConfigMetrics() (*data.GenericAPIResponse, error) { + if epcm.GetNetworkConfigMetricsCalled != nil { + return epcm.GetNetworkConfigMetricsCalled() + } + return nil, nil +} + +// GetNetworkStatusMetrics - +func (epcm *ElrondProxyClientMock) GetNetworkStatusMetrics(_ uint32) (*data.GenericAPIResponse, error) { + return nil, nil +} + +// GetBlockByNonce - +func (epcm *ElrondProxyClientMock) GetBlockByNonce(shardID uint32, nonce uint64, withTxs bool) (*data.BlockApiResponse, error) { + if epcm.GetBlockByNonceCalled != nil { + return epcm.GetBlockByNonceCalled(shardID, nonce, withTxs) + } + return nil, nil +} + +// GetAccount - +func (epcm *ElrondProxyClientMock) GetAccount(address string) (*data.Account, error) { + if epcm.GetAccountCalled != nil { + return epcm.GetAccountCalled(address) + } + return nil, nil +} + +// GetHyperBlockByNonce - +func (epcm *ElrondProxyClientMock) GetHyperBlockByNonce(nonce uint64) (*data.HyperblockApiResponse, error) { + if epcm.GetHyperBlockByNonceCalled != nil { + return epcm.GetHyperBlockByNonceCalled(nonce) + } + return nil, nil +} + +// GetHyperBlockByHash - +func (epcm *ElrondProxyClientMock) GetHyperBlockByHash(hash string) (*data.HyperblockApiResponse, error) { + if epcm.GetHyperBlockByHashCalled != nil { + return epcm.GetHyperBlockByHashCalled(hash) + } + return nil, nil +} + +// SendTransaction - +func (epcm *ElrondProxyClientMock) SendTransaction(tx *data.Transaction) (int, string, error) { + if epcm.SendTransactionCalled != nil { + return epcm.SendTransactionCalled(tx) + } + return 0, "", nil +} + +// ComputeTransactionHash - +func (epcm *ElrondProxyClientMock) ComputeTransactionHash(hash *data.Transaction) (string, error) { + if epcm.ComputeTransactionHashCalled != nil { + return epcm.ComputeTransactionHashCalled(hash) + } + return "", nil +} + +// GetAddressConverter - +func (epcm *ElrondProxyClientMock) GetAddressConverter() (core.PubkeyConverter, error) { + if epcm.GetAddressConverterCalled != nil { + return epcm.GetAddressConverterCalled() + } + return nil, nil +} + +// GetLatestBlockNonce - +func (epcm *ElrondProxyClientMock) GetLatestFullySynchronizedHyperblockNonce() (uint64, error) { + if epcm.GetLatestFullySynchronizedHyperblockNonceCalled != nil { + return epcm.GetLatestFullySynchronizedHyperblockNonceCalled() + } + return 0, nil +} + +// GetTransactionByHashAndSenderAddress - +func (epcm *ElrondProxyClientMock) GetTransactionByHashAndSenderAddress(hash string, sndAddr string) (*data.FullTransaction, int, error) { + if epcm.GetTransactionByHashAndSenderAddressCalled != nil { + return epcm.GetTransactionByHashAndSenderAddressCalled(hash, sndAddr) + } + return nil, 0, nil +} diff --git a/rosetta/rosetta-cli-config/mainnet/elrond_mainnet.json b/rosetta/rosetta-cli-config/mainnet/elrond_mainnet.json new file mode 100644 index 00000000..8febb9a5 --- /dev/null +++ b/rosetta/rosetta-cli-config/mainnet/elrond_mainnet.json @@ -0,0 +1,176 @@ +{ + "network": { + "blockchain": "Elrond", + "network": "1" + }, + "online_url": "http://10.0.0.2:8079", + "data_directory": "", + "http_timeout": 200, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 120, + "max_sync_concurrency": 1, + "tip_delay": 1, + "log_configuration": false, + "data": { + "active_reconciliation_concurrency": 0, + "inactive_reconciliation_concurrency": 0, + "inactive_reconciliation_frequency": 0, + "log_blocks": false, + "log_transactions": false, + "log_balance_changes": false, + "log_reconciliations": false, + "ignore_reconciliation_error": true, + "exempt_accounts": "", + "bootstrap_balances": "", + "interesting_accounts": "", + "reconciliation_disabled": false, + "inactive_discrepency_search_disabled": false, + "balance_tracking_disabled": true, + "coin_tracking_disabled": false, + "results_output_file": "" + }, + "construction": { + "offline_url": "http://10.0.0.2:8079", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": true, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "workflows": [ + { + "name": "request_funds", + "concurrency": 1, + "scenarios": [ + { + "name": "find_account", + "actions": [ + { + "input": "{\"symbol\":\"eGLD\", \"decimals\":18}", + "type": "set_variable", + "output_path": "currency" + }, + { + "input": "{\"minimum_balance\":{\"value\": \"0\", \"currency\": {{currency}}}, \"create_limit\":1}", + "type": "find_balance", + "output_path": "random_account" + } + ] + }, + { + "name": "request", + "actions": [ + { + "input": "{\"account_identifier\": {{random_account.account_identifier}}, \"minimum_balance\":{\"value\": \"1000000000000000000\", \"currency\": {{currency}}}}", + "type": "find_balance", + "output_path": "loaded_account" + } + ] + } + ] + }, + { + "name": "create_account", + "concurrency": 1, + "scenarios": [ + { + "name": "create_account", + "actions": [ + { + "input": "{\"network\":\"1\", \"blockchain\":\"Elrond\"}", + "type": "set_variable", + "output_path": "network" + }, + { + "input": "{\"curve_type\": \"edwards25519\"}", + "type": "generate_key", + "output_path": "key" + }, + { + "input": "{\"network_identifier\": {{network}}, \"public_key\": {{key.public_key}}}", + "type": "derive", + "output_path": "account" + }, + { + "input": "{\"account_identifier\": {{account.account_identifier}}, \"keypair\": {{key}}}", + "type": "save_account" + } + ] + } + ] + }, + { + "name": "transfer", + "concurrency": 10, + "scenarios": [ + { + "name": "Transfer", + "actions": [ + { + "input": "{\"network\":\"1\", \"blockchain\":\"Elrond\"}", + "type": "set_variable", + "output_path": "Transfer.network" + }, + { + "input": "{\"symbol\":\"eGLD\", \"decimals\":18}", + "type": "set_variable", + "output_path": "currency" + }, + { + "input": "{\"minimum_balance\":{\"value\": \"1000\", \"currency\": {{currency}}}}", + "type": "find_balance", + "output_path": "sender" + }, + { + "input": "\"100000000\"", + "type": "set_variable", + "output_path": "max_fee" + }, + { + "input": "{\"operation\":\"subtraction\", \"left_value\": {{sender.balance.value}}, \"right_value\": {{max_fee}}}", + "type": "math", + "output_path": "available_amount" + }, + { + "input": "{\"minimum\": \"1\", \"maximum\": {{available_amount}}}", + "type": "random_number", + "output_path": "recipient_amount" + }, + { + "input": "{\"recipient_amount\":{{recipient_amount}}}", + "type": "print_message" + }, + { + "input": "{\"operation\":\"subtraction\", \"left_value\": \"0\", \"right_value\":{{recipient_amount}}}", + "type": "math", + "output_path": "sender_amount" + }, + { + "input": "{\"not_account_identifier\":[{{sender.account_identifier}}], \"minimum_balance\":{\"value\": \"0\", \"currency\": {{currency}}}, \"create_limit\": 100, \"create_probability\": 50}", + "type": "find_balance", + "output_path": "recipient" + }, + { + "input": "\"1\"", + "type": "set_variable", + "output_path": "Transfer.confirmation_depth" + }, + { + "input": "[{\"operation_identifier\":{\"index\":0},\"type\":\"Transfer\",\"account\":{{sender.account_identifier}},\"amount\":{\"value\":{{sender_amount}},\"currency\":{{currency}}}},{\"operation_identifier\":{\"index\":1},\"type\":\"Transfer\",\"account\":{{recipient.account_identifier}},\"amount\":{\"value\":{{recipient_amount}},\"currency\":{{currency}}}}]", + "type": "set_variable", + "output_path": "Transfer.operations" + } + ] + } + ] + } + ], + "end_conditions": { + "create_account": 2, + "transfer": 2 + } + } +} diff --git a/rosetta/rosetta-cli-config/testnet/elrond_testnet.json b/rosetta/rosetta-cli-config/testnet/elrond_testnet.json new file mode 100644 index 00000000..9ad73baa --- /dev/null +++ b/rosetta/rosetta-cli-config/testnet/elrond_testnet.json @@ -0,0 +1,177 @@ +{ + "network": { + "blockchain": "Elrond", + "network": "T" + }, + "online_url": "http://10.0.0.2:8079", + "data_directory": "", + "http_timeout": 200, + "max_retries": 5, + "retry_elapsed_time": 0, + "max_online_connections": 120, + "max_sync_concurrency": 1, + "tip_delay": 1, + "log_configuration": false, + "data": { + "active_reconciliation_concurrency": 0, + "inactive_reconciliation_concurrency": 0, + "inactive_reconciliation_frequency": 0, + "log_blocks": false, + "log_transactions": false, + "log_balance_changes": false, + "log_reconciliations": false, + "ignore_reconciliation_error": true, + "exempt_accounts": "", + "bootstrap_balances": "", + "interesting_accounts": "", + "reconciliation_disabled": false, + "inactive_discrepency_search_disabled": false, + "balance_tracking_disabled": true, + "coin_tracking_disabled": false, + "results_output_file": "" + }, + "construction": { + "offline_url": "http://10.0.0.2:8079", + "max_offline_connections": 0, + "stale_depth": 0, + "broadcast_limit": 0, + "ignore_broadcast_failures": false, + "clear_broadcasts": false, + "broadcast_behind_tip": true, + "block_broadcast_limit": 0, + "rebroadcast_all": false, + "workflows": [ + { + "name": "request_funds", + "concurrency": 1, + "scenarios": [ + { + "name": "find_account", + "actions": [ + { + "input": "{\"symbol\":\"XeGLD\", \"decimals\":18}", + "type": "set_variable", + "output_path": "currency" + }, + { + "input": "{\"minimum_balance\":{\"value\": \"0\", \"currency\": {{currency}}}, \"create_limit\":1}", + "type": "find_balance", + "output_path": "random_account" + } + ] + }, + { + "name": "request", + "actions": [ + { + "input": "{\"account_identifier\": {{random_account.account_identifier}}, \"minimum_balance\":{\"value\": \"1000000000000000000\", \"currency\": {{currency}}}}", + "type": "find_balance", + "output_path": "loaded_account" + } + ] + } + ] + }, + { + "name": "create_account", + "concurrency": 1, + "scenarios": [ + { + "name": "create_account", + "actions": [ + { + "input": "{\"network\":\"T\", \"blockchain\":\"Elrond\"}", + "type": "set_variable", + "output_path": "network" + }, + { + "input": "{\"curve_type\": \"edwards25519\"}", + "type": "generate_key", + "output_path": "key" + }, + { + "input": "{\"network_identifier\": {{network}}, \"public_key\": {{key.public_key}}}", + "type": "derive", + "output_path": "account" + }, + { + "input": "{\"account_identifier\": {{account.account_identifier}}, \"keypair\": {{key}}}", + "type": "save_account" + } + ] + } + ] + }, + { + "name": "transfer", + "concurrency": 10, + "scenarios": [ + { + "name": "Transfer", + "actions": [ + { + "input": "{\"network\":\"T\", \"blockchain\":\"Elrond\"}", + "type": "set_variable", + "output_path": "Transfer.network" + }, + { + "input": "{\"symbol\":\"XeGLD\", \"decimals\":18}", + "type": "set_variable", + "output_path": "currency" + }, + { + "input": "{\"minimum_balance\":{\"value\": \"1000\", \"currency\": {{currency}}}}", + "type": "find_balance", + "output_path": "sender" + }, + { + "input": "\"100000000\"", + "type": "set_variable", + "output_path": "max_fee" + }, + { + "input": "{\"operation\":\"subtraction\", \"left_value\": {{sender.balance.value}}, \"right_value\": {{max_fee}}}", + "type": "math", + "output_path": "available_amount" + }, + { + "input": "{\"minimum\": \"1\", \"maximum\": {{available_amount}}}", + "type": "random_number", + "output_path": "recipient_amount" + }, + { + "input": "{\"recipient_amount\":{{recipient_amount}}}", + "type": "print_message" + }, + { + "input": "{\"operation\":\"subtraction\", \"left_value\": \"0\", \"right_value\":{{recipient_amount}}}", + "type": "math", + "output_path": "sender_amount" + }, + { + "input": "{\"not_account_identifier\":[{{sender.account_identifier}}], \"minimum_balance\":{\"value\": \"0\", \"currency\": {{currency}}}, \"create_limit\": 100, \"create_probability\": 50}", + "type": "find_balance", + "output_path": "recipient" + }, + { + "input": "\"1\"", + "type": "set_variable", + "output_path": "Transfer.confirmation_depth" + }, + { + "input": "[{\"operation_identifier\":{\"index\":0},\"type\":\"Transfer\",\"account\":{{sender.account_identifier}},\"amount\":{\"value\":{{sender_amount}},\"currency\":{{currency}}}},{\"operation_identifier\":{\"index\":1},\"type\":\"Transfer\",\"account\":{{recipient.account_identifier}},\"amount\":{\"value\":{{recipient_amount}},\"currency\":{{currency}}}}]", + "type": "set_variable", + "output_path": "Transfer.operations" + } + ] + } + ] + } + ], + "end_conditions": { + "create_account": 2, + "transfer": 2 + } + } +} + diff --git a/rosetta/rosetta.sh b/rosetta/rosetta.sh new file mode 100755 index 00000000..460604d4 --- /dev/null +++ b/rosetta/rosetta.sh @@ -0,0 +1,121 @@ +#!/bin/bash +set -e + +#Color to the people +RED='\x1B[0;31m' +CYAN='\x1B[0;36m' +GREEN='\x1B[0;32m' +NC='\x1B[0m' + +#Variables +declare -a NODES=("0" "1" "2" "metachain") +GIT_HOME=~/observing-squad +STACK_FOLDER_MAINNET=~/MyObservingSquad +KEYS_FOLDER_MAINNET=${STACK_FOLDER_MAINNET}/keys +STACK_FOLDER_TESTNET=~/MyObservingSquadTestnet +KEYS_FOLDER_TESTNET=${STACK_FOLDER_TESTNET}/keys + +if [[ -f ~/.proxyDockerTag ]]; then + PROXY_TAG=$(cat ~/.proxyDockerTag) +fi + +case "$1" in + +'mainnet') + +#Create the folder structure for the observer stack +mkdir -p ${STACK_FOLDER_MAINNET}/{proxy,node-0,node-1,node-2,node-metachain}/{config,logs} +mkdir -p ${STACK_FOLDER_MAINNET}/{node-0,node-1,node-2,node-metachain}/db +mkdir -p ${KEYS_FOLDER_MAINNET} + +#Clone the repo and cd there +if [ -d "$GIT_HOME" ]; then sudo rm -rf $GIT_HOME; fi +git clone -b master https://github.com/ElrondNetwork/observing-squad.git $GIT_HOME +cd $GIT_HOME/rosetta-mainnet + +if [[ -f ~/.proxyDockerTag ]]; then + sed -i '/PROXY_TAG/d' .env + echo PROXY_TAG=${PROXY_TAG} >> .env +fi + +#Generate Keys and place them in their respective folders + +for OBSERVER_MAINNET in "${NODES[@]}" +do + echo -e "${GREEN}--> Generating key for observer ${CYAN}$OBSERVER_MAINNET${GREEN}...${NC}" + docker run --rm --mount type=bind,source=${KEYS_FOLDER_MAINNET},destination=/keys --workdir /keys elrondnetwork/elrond-go-keygenerator:latest && sudo chown $(whoami) ${KEYS_FOLDER_MAINNET}/validatorKey.pem && mv ${KEYS_FOLDER_MAINNET}/validatorKey.pem ${STACK_FOLDER_MAINNET}/node-$OBSERVER_MAINNET/config/observerKey_$OBSERVER_MAINNET.pem +done + +#Say what has been started +echo "mainnet" > ~/.squadlocation + +#Start the stack +echo -e +echo -e "${GREEN}--> Starting the Observer+Proxy MainNet Stack...${NC}" +echo -e +docker-compose --env-file .env up -d +;; + +'testnet') +#Create the folder structure for the observer stack +mkdir -p ${STACK_FOLDER_TESTNET}/{proxy,node-0,node-1,node-2,node-metachain}/{config,logs} +mkdir -p ${STACK_FOLDER_TESTNET}/{node-0,node-1,node-2,node-metachain}/db +mkdir -p ${KEYS_FOLDER_TESTNET} + +#Clone the repo and cd there +if [ -d "$GIT_HOME" ]; then sudo rm -rf $GIT_HOME; fi +git clone -b master https://github.com/ElrondNetwork/observing-squad.git $GIT_HOME +cd $GIT_HOME/rosetta-testnet + +if [[ -f ~/.proxyDockerTag ]]; then + sed -i '/PROXY_TAG/d' .env + echo PROXY_TAG=${PROXY_TAG} >> .env +fi + +#Generate Keys and place them in their respective folders + +for OBSERVER_TESTNET in "${NODES[@]}" +do + echo -e "${GREEN}--> Generating key for observer ${CYAN}$OBSERVER_TESTNET${GREEN}...${NC}" + docker run --rm --mount type=bind,source=${KEYS_FOLDER_TESTNET},destination=/keys --workdir /keys elrondnetwork/elrond-go-keygenerator:latest && sudo chown $(whoami) ${KEYS_FOLDER_TESTNET}/validatorKey.pem && mv ${KEYS_FOLDER_TESTNET}/validatorKey.pem ${STACK_FOLDER_TESTNET}/node-$OBSERVER_TESTNET/config/observerKey_$OBSERVER_TESTNET.pem +done + +#Say what has been started +echo "testnet" > ~/.squadlocation + +#Start the stack +echo -e +echo -e "${GREEN}--> Starting the Observer+Proxy TestNet Stack...${NC}" +echo -e +docker-compose --env-file .env up -d +;; + +'stop') +read -p "Do you want to stop the stack ? [y/n] :" STOP + +if [[ $STOP = y ]] ; then + if grep -Fxq "mainnet" ~/.squadlocation + then + echo -e + echo -e "${RED}--> Stopping the Observer+Proxy Stack...${NC}" + echo -e + cd $GIT_HOME/rosetta-mainnet && docker-compose down + else + echo -e + echo -e "${RED}--> Stopping the Observer+Proxy Stack...${NC}" + echo -e + cd $GIT_HOME/rosetta-testnet && docker-compose down + fi + else + echo -e + echo -e "${GREEN}--> Ok...ignoring the command...${NC}" + echo -e +fi + +;; + +*) + echo "Usage: Missing parameter ! [mainnet|testnet|stop]" +;; + +esac diff --git a/rosetta/services/account_service.go b/rosetta/services/account_service.go new file mode 100644 index 00000000..2bcd3592 --- /dev/null +++ b/rosetta/services/account_service.go @@ -0,0 +1,62 @@ +package services + +import ( + "context" + + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +type accountAPIService struct { + elrondProvider provider.ElrondProviderHandler + config *configuration.Configuration +} + +// NewAccountAPIService will create a new instance of accountAPIService +func NewAccountAPIService(elrondProvider provider.ElrondProviderHandler, cfg *configuration.Configuration) server.AccountAPIServicer { + return &accountAPIService{ + elrondProvider: elrondProvider, + config: cfg, + } +} + +// AccountBalance implements the /account/balance endpoint. +func (aas *accountAPIService) AccountBalance( + _ context.Context, + request *types.AccountBalanceRequest, +) (*types.AccountBalanceResponse, *types.Error) { + // TODO cannot return balance at a specific nonce right now + if request.AccountIdentifier.Address == "" { + return nil, ErrInvalidAccountAddress + } + + latestBlockData, err := aas.elrondProvider.GetLatestBlockData() + if err != nil { + return nil, wrapErr(ErrUnableToGetBlock, err) + } + + account, err := aas.elrondProvider.GetAccount(request.AccountIdentifier.Address) + if err != nil { + return nil, wrapErr(ErrUnableToGetAccount, err) + } + + response := &types.AccountBalanceResponse{ + BlockIdentifier: &types.BlockIdentifier{ + Index: int64(latestBlockData.Nonce), + Hash: latestBlockData.Hash, + }, + Balances: []*types.Amount{ + { + Value: account.Balance, + Currency: aas.config.Currency, + }, + }, + Metadata: map[string]interface{}{ + "nonce": account.Nonce, + }, + } + + return response, nil +} diff --git a/rosetta/services/account_service_test.go b/rosetta/services/account_service_test.go new file mode 100644 index 00000000..00becf87 --- /dev/null +++ b/rosetta/services/account_service_test.go @@ -0,0 +1,59 @@ +package services + +import ( + "context" + "testing" + + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/assert" +) + +func TestAccountAPIService_AccountBalance(t *testing.T) { + t.Parallel() + + address := "erd13lx7zldumunqvf74g5z407gwl5r35jha06rjzc32qcujamknzdgsnt2yvn" + accountBalance := "1234" + latestBlockNonce := uint64(1) + lastestBlockHash := "hash-hash-hash" + elrondProviderMock := &mocks.ElrondProviderMock{ + GetAccountCalled: func(address string) (*data.Account, error) { + return &data.Account{ + Address: "erd13lx7zldumunqvf74g5z407gwl5r35jha06rjzc32qcujamknzdgsnt2yvn", + Nonce: 1, + Balance: "1234", + }, nil + }, + GetLatestBlockDataCalled: func() (*provider.BlockData, error) { + return &provider.BlockData{ + Nonce: latestBlockNonce, + Hash: lastestBlockHash, + }, nil + }, + } + cfg := &configuration.Configuration{} + + accountAPIService := NewAccountAPIService(elrondProviderMock, cfg) + assert.NotNil(t, accountAPIService) + + _, err := accountAPIService.AccountBalance(context.Background(), &types.AccountBalanceRequest{ + AccountIdentifier: &types.AccountIdentifier{ + Address: "", + }, + }) + assert.Equal(t, ErrInvalidAccountAddress, err) + + // Get account balance should work + accountBalanceResponse, err := accountAPIService.AccountBalance(context.Background(), &types.AccountBalanceRequest{ + AccountIdentifier: &types.AccountIdentifier{ + Address: address, + }, + }) + assert.Nil(t, err) + assert.Equal(t, accountBalance, accountBalanceResponse.Balances[0].Value) + assert.Equal(t, lastestBlockHash, accountBalanceResponse.BlockIdentifier.Hash) + assert.Equal(t, int64(latestBlockNonce), accountBalanceResponse.BlockIdentifier.Index) +} diff --git a/rosetta/services/block_service.go b/rosetta/services/block_service.go new file mode 100644 index 00000000..0f489e1a --- /dev/null +++ b/rosetta/services/block_service.go @@ -0,0 +1,97 @@ +package services + +import ( + "context" + + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +type blockAPIService struct { + elrondProvider provider.ElrondProviderHandler + txsParser *transactionsParser +} + +// NewBlockAPIService will create a new instance of blockAPIService +func NewBlockAPIService( + elrondProvider provider.ElrondProviderHandler, + cfg *configuration.Configuration, + networkConfig *provider.NetworkConfig, +) server.BlockAPIServicer { + return &blockAPIService{ + elrondProvider: elrondProvider, + txsParser: newTransactionParser(elrondProvider, cfg, networkConfig), + } +} + +// Block implements the /block endpoint. +func (bas *blockAPIService) Block( + _ context.Context, + request *types.BlockRequest, +) (*types.BlockResponse, *types.Error) { + if request.BlockIdentifier.Index != nil { + return bas.getBlockByNonce(*request.BlockIdentifier.Index) + } + + if request.BlockIdentifier.Hash != nil { + return bas.getBlockByHash(*request.BlockIdentifier.Hash) + } + + return nil, ErrMustQueryByIndexOrByHash +} + +func (bas *blockAPIService) getBlockByNonce(nonce int64) (*types.BlockResponse, *types.Error) { + hyperBlock, err := bas.elrondProvider.GetBlockByNonce(nonce) + if err != nil { + return nil, wrapErr(ErrUnableToGetBlock, err) + } + + return bas.parseHyperBlock(hyperBlock) +} + +func (bas *blockAPIService) getBlockByHash(hash string) (*types.BlockResponse, *types.Error) { + hyperBlock, err := bas.elrondProvider.GetBlockByHash(hash) + if err != nil { + return nil, wrapErr(ErrUnableToGetBlock, err) + } + + return bas.parseHyperBlock(hyperBlock) +} + +func (bas *blockAPIService) parseHyperBlock(hyperBlock *data.Hyperblock) (*types.BlockResponse, *types.Error) { + var parentBlockIdentifier *types.BlockIdentifier + if hyperBlock.Nonce != 0 { + parentBlockIdentifier = &types.BlockIdentifier{ + Index: int64(hyperBlock.Nonce - 1), + Hash: hyperBlock.PrevBlockHash, + } + } + + return &types.BlockResponse{ + Block: &types.Block{ + BlockIdentifier: &types.BlockIdentifier{ + Index: int64(hyperBlock.Nonce), + Hash: hyperBlock.Hash, + }, + ParentBlockIdentifier: parentBlockIdentifier, + Timestamp: bas.elrondProvider.CalculateBlockTimestampUnix(hyperBlock.Round), + Transactions: bas.txsParser.parseTxsFromHyperBlock(hyperBlock), + Metadata: objectsMap{ + "epoch": hyperBlock.Epoch, + "round": hyperBlock.Round, + }, + }, + }, nil +} + +// BlockTransaction - not implemented +// We dont need this method because all transactions are returned by method Block +func (bas *blockAPIService) BlockTransaction( + _ context.Context, + _ *types.BlockTransactionRequest, +) (*types.BlockTransactionResponse, *types.Error) { + return nil, ErrNotImplemented +} diff --git a/rosetta/services/block_service_test.go b/rosetta/services/block_service_test.go new file mode 100644 index 00000000..94beb6d2 --- /dev/null +++ b/rosetta/services/block_service_test.go @@ -0,0 +1,195 @@ +package services + +import ( + "context" + "testing" + + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/assert" +) + +func TestBlockAPIService_BlockByIndex(t *testing.T) { + t.Parallel() + + blockIndex := int64(10) + round := uint64(12) + blockHash := "hash-hash-hash" + prevBlockHash := "prev-hash-hash-hash" + + txHash := "txHash" + fullTxNormal := &data.FullTransaction{ + Hash: txHash, + Type: string(transaction.TxTypeNormal), + Receiver: "erd1uml89f3lqqfxan67dnnlytd0r3mz3v684zxdhqq60gs5u7qa9yjqa5dgqp", + Sender: "erd18f33a94auxr4v8v23wu8gwv7mzf408jsskktvj4lcmcrv4v5jmqs5x3kdn", + Value: "1234", + GasLimit: 100, + GasPrice: 10, + } + + rewardHash := "rewardHash" + rewardTx := &data.FullTransaction{ + Hash: rewardHash, + Receiver: "erd1uml89f3lqqfxan67dnnlytd0r3mz3v684zxdhqq60gs5u7qa9yjqa5dgqp", + Value: "1111", + Type: string(transaction.TxTypeReward), + } + + invalidTxHash := "invalidTx" + invalidTx := &data.FullTransaction{ + Hash: invalidTxHash, + Sender: "erd1uml89f3lqqfxan67dnnlytd0r3mz3v684zxdhqq60gs5u7qa9yjqa5dgqp", + GasLimit: 100, + GasPrice: 10, + Type: string(transaction.TxTypeInvalid), + } + + elrondProviderMock := &mocks.ElrondProviderMock{ + GetBlockByNonceCalled: func(nonce int64) (*data.Hyperblock, error) { + return &data.Hyperblock{ + Nonce: uint64(blockIndex), + Hash: blockHash, + Round: round, + PrevBlockHash: prevBlockHash, + Transactions: []*data.FullTransaction{ + fullTxNormal, rewardTx, invalidTx, + }, + }, nil + }, + } + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + blockAPIService := NewBlockAPIService(elrondProviderMock, cfg, networkCfg) + tp := newTransactionParser(elrondProviderMock, cfg, networkCfg) + + expectedBlock := &types.Block{ + BlockIdentifier: &types.BlockIdentifier{ + Index: blockIndex, + Hash: blockHash, + }, + ParentBlockIdentifier: &types.BlockIdentifier{ + Index: blockIndex - 1, + Hash: prevBlockHash, + }, + Timestamp: 0, + Transactions: []*types.Transaction{ + { + TransactionIdentifier: &types.TransactionIdentifier{Hash: txHash}, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: fullTxNormal.Sender, + }, + Amount: &types.Amount{ + Value: "-" + fullTxNormal.Value, + Currency: nil, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: fullTxNormal.Receiver, + }, + Amount: &types.Amount{ + Value: fullTxNormal.Value, + Currency: nil, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 2, + }, + Type: opFee, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: fullTxNormal.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tp.computeTxFee(fullTxNormal), + Currency: nil, + }, + }, + }, + }, + { + + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: rewardTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opReward, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: rewardTx.Receiver, + }, + Amount: &types.Amount{ + Value: rewardTx.Value, + Currency: tp.config.Currency, + }, + }, + }, + }, + { + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: invalidTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opInvalid, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: invalidTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tp.computeTxFee(invalidTx), + Currency: tp.config.Currency, + }, + }, + }, + }, + }, + Metadata: objectsMap{ + "epoch": uint32(0), + "round": round, + }, + } + blockResponse, err := blockAPIService.Block(context.Background(), &types.BlockRequest{ + NetworkIdentifier: nil, + BlockIdentifier: &types.PartialBlockIdentifier{ + Index: &blockIndex, + Hash: nil, + }, + }) + assert.Nil(t, err) + assert.Equal(t, expectedBlock, blockResponse.Block) +} diff --git a/rosetta/services/common.go b/rosetta/services/common.go new file mode 100644 index 00000000..2da85dde --- /dev/null +++ b/rosetta/services/common.go @@ -0,0 +1,8 @@ +package services + +// SupportedOperationTypes is a list of the supported operations. +var SupportedOperationTypes = []string{ + opTransfer, opFee, opReward, opScResult, opInvalid, +} + +type objectsMap map[string]interface{} diff --git a/rosetta/services/constants.go b/rosetta/services/constants.go new file mode 100644 index 00000000..c96078b8 --- /dev/null +++ b/rosetta/services/constants.go @@ -0,0 +1,19 @@ +package services + +const ( + NumBlocksToGet = uint64(200) + + RosettaVersion = "1.4.5" + NodeVersion = "1.1.0" + + // OpStatusOK is the operation status for successful operations. + OpStatusSuccess = "Success" + // OpStatusFailed is the operation status for failed operations. + OpStatusFailed = "Failed" + + opTransfer = "Transfer" + opFee = "Fee" + opReward = "Reward" + opScResult = "SmartContractResult" + opInvalid = "Invalid" +) diff --git a/rosetta/services/construction_service.go b/rosetta/services/construction_service.go new file mode 100644 index 00000000..cd2721cc --- /dev/null +++ b/rosetta/services/construction_service.go @@ -0,0 +1,391 @@ +package services + +import ( + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +type constructionAPIService struct { + elrondProvider provider.ElrondProviderHandler + config *configuration.Configuration + txsParser *transactionsParser + networkConfig *provider.NetworkConfig +} + +// NewConstructionAPIService creates a new instance of an constructionAPIService. +func NewConstructionAPIService( + elrondProvider provider.ElrondProviderHandler, + cfg *configuration.Configuration, + networkConfig *provider.NetworkConfig, +) server.ConstructionAPIServicer { + return &constructionAPIService{ + elrondProvider: elrondProvider, + config: cfg, + txsParser: newTransactionParser(elrondProvider, cfg, networkConfig), + networkConfig: networkConfig, + } +} + +//ConstructionPreprocess will preprocess data that in provided in request +func (cas *constructionAPIService) ConstructionPreprocess( + _ context.Context, + request *types.ConstructionPreprocessRequest, +) (*types.ConstructionPreprocessResponse, *types.Error) { + if err := cas.checkOperationsAndMeta(request.Operations, request.Metadata); err != nil { + return nil, err + } + + options, err := getOptionsFromOperations(request.Operations) + if err != nil { + return nil, err + } + + if len(request.MaxFee) > 0 { + maxFee := request.MaxFee[0] + if maxFee.Currency.Symbol != cas.config.Currency.Symbol || + maxFee.Currency.Decimals != cas.config.Currency.Decimals { + return nil, wrapErr(ErrConstructionCheck, errors.New("invalid currency")) + } + + options["maxFee"] = maxFee.Value + } + + if request.SuggestedFeeMultiplier != nil { + options["feeMultiplier"] = *request.SuggestedFeeMultiplier + } + + if request.Metadata["gasLimit"] != nil { + options["gasLimit"] = request.Metadata["gasLimit"] + } + if request.Metadata["gasPrice"] != nil { + options["gasPrice"] = request.Metadata["gasPrice"] + } + if request.Metadata["data"] != nil { + options["data"] = request.Metadata["data"] + } + + return &types.ConstructionPreprocessResponse{ + Options: options, + }, nil +} + +func (cas *constructionAPIService) checkOperationsAndMeta(ops []*types.Operation, meta map[string]interface{}) *types.Error { + if len(ops) == 0 { + return wrapErr(ErrConstructionCheck, errors.New("invalid number of operations")) + } + + for _, op := range ops { + if !checkOperationsType(op) { + return wrapErr(ErrConstructionCheck, errors.New("unsupported operation type")) + } + if op.Amount.Currency.Symbol != cas.config.Currency.Symbol { + return wrapErr(ErrConstructionCheck, errors.New("unsupported currency symbol")) + } + } + + if meta["gasLimit"] != nil { + if _, ok := meta["gasLimit"].(uint64); !ok { + return wrapErr(ErrConstructionCheck, errors.New("invalid metadata gas limit")) + } + } + if meta["gasPrice"] != nil { + if _, ok := meta["gasPrice"].(uint64); !ok { + return wrapErr(ErrConstructionCheck, errors.New("invalid metadata gas price")) + } + } + + return nil +} + +func checkOperationsType(op *types.Operation) bool { + for _, supOp := range SupportedOperationTypes { + if supOp == op.Type { + return true + } + } + + return false +} + +func getOptionsFromOperations(ops []*types.Operation) (objectsMap, *types.Error) { + if len(ops) < 2 { + return nil, wrapErr(ErrConstructionCheck, errors.New("invalid number of operations")) + } + options := make(objectsMap) + options["sender"] = ops[0].Account.Address + options["receiver"] = ops[1].Account.Address + options["type"] = ops[0].Type + options["value"] = ops[1].Amount.Value + + return options, nil +} + +// ConstructionMetadata construct metadata for a transaction +func (cas *constructionAPIService) ConstructionMetadata( + _ context.Context, + request *types.ConstructionMetadataRequest, +) (*types.ConstructionMetadataResponse, *types.Error) { + txType, ok := request.Options["type"].(string) + if !ok { + return nil, wrapErr(ErrInvalidInputParam, errors.New("invalid operation type")) + } + + metadata, errS := cas.computeMetadata(request.Options) + if errS != nil { + return nil, errS + } + + suggestedFee, gasPrice, gasLimit, errS := computeSuggestedFeeAndGas(txType, request.Options, cas.networkConfig) + if errS != nil { + return nil, errS + } + + metadata["gasLimit"] = gasLimit + metadata["gasPrice"] = gasPrice + + return &types.ConstructionMetadataResponse{ + Metadata: metadata, + SuggestedFee: []*types.Amount{ + { + Value: suggestedFee.String(), + Currency: cas.config.Currency, + }, + }, + }, nil +} + +func (cas *constructionAPIService) computeMetadata(options objectsMap) (objectsMap, *types.Error) { + metadata := make(objectsMap) + if dataField, ok := options["data"]; ok { + // convert string to byte array + metadata["data"] = []byte(fmt.Sprintf("%v", dataField)) + } + + var ok bool + if metadata["sender"], ok = options["sender"]; !ok { + return nil, wrapErr(ErrMalformedValue, errors.New("sender address missing")) + } + if metadata["receiver"], ok = options["receiver"]; !ok { + return nil, wrapErr(ErrMalformedValue, errors.New("receiver address missing")) + } + if metadata["value"], ok = options["value"]; !ok { + return nil, wrapErr(ErrMalformedValue, errors.New("value missing")) + } + + metadata["chainID"] = cas.networkConfig.ChainID + metadata["version"] = cas.networkConfig.MinTxVersion + + senderAddressI, ok := options["sender"] + if !ok { + return nil, wrapErr(ErrInvalidInputParam, errors.New("cannot find sender address")) + } + senderAddress, ok := senderAddressI.(string) + if !ok { + return nil, wrapErr(ErrMalformedValue, errors.New("sender address is invalid")) + } + + account, err := cas.elrondProvider.GetAccount(senderAddress) + if err != nil { + return nil, wrapErr(ErrUnableToGetAccount, err) + } + + metadata["nonce"] = account.Nonce + + return metadata, nil +} + +// ConstructionPayloads will prepare a transaction for signing +func (cas *constructionAPIService) ConstructionPayloads( + _ context.Context, + request *types.ConstructionPayloadsRequest, +) (*types.ConstructionPayloadsResponse, *types.Error) { + if err := cas.checkOperationsAndMeta(request.Operations, request.Metadata); err != nil { + return nil, err + } + + erdTx, err := createTransaction(request) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + mtx, err := json.Marshal(erdTx) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + unsignedTx := hex.EncodeToString(mtx) + + return &types.ConstructionPayloadsResponse{ + UnsignedTransaction: unsignedTx, + Payloads: []*types.SigningPayload{ + { + AccountIdentifier: &types.AccountIdentifier{ + Address: request.Operations[0].Account.Address, + }, + SignatureType: types.Ed25519, + Bytes: mtx, + }, + }, + }, nil +} + +// ConstructionParse will check if a transaction is correctly formatted +func (cas *constructionAPIService) ConstructionParse( + _ context.Context, + request *types.ConstructionParseRequest, +) (*types.ConstructionParseResponse, *types.Error) { + elrondTx, err := getTxFromRequest(request.Transaction) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + var signers []*types.AccountIdentifier + if request.Signed { + signers = []*types.AccountIdentifier{ + { + Address: elrondTx.Sender, + }, + } + } + + return &types.ConstructionParseResponse{ + Operations: cas.txsParser.createOperationsFromPreparedTx(elrondTx), + AccountIdentifierSigners: signers, + }, nil +} + +func createTransaction(request *types.ConstructionPayloadsRequest) (*data.Transaction, error) { + tx := &data.Transaction{} + + requestMetadataBytes, err := json.Marshal(request.Metadata) + if err != nil { + return nil, err + } + + err = json.Unmarshal(requestMetadataBytes, tx) + if err != nil { + return nil, err + } + + return tx, nil +} + +func getTxFromRequest(txString string) (*data.Transaction, error) { + txBytes, err := hex.DecodeString(txString) + if err != nil { + return nil, err + } + + var elrondTx data.Transaction + err = json.Unmarshal(txBytes, &elrondTx) + if err != nil { + return nil, err + } + + return &elrondTx, nil +} + +//ConstructionCombine will create a signed transaction for transaction bytes and signature +func (cas *constructionAPIService) ConstructionCombine( + _ context.Context, + request *types.ConstructionCombineRequest, +) (*types.ConstructionCombineResponse, *types.Error) { + elrondTx, err := getTxFromRequest(request.UnsignedTransaction) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + if len(request.Signatures) != 1 { + return nil, ErrInvalidInputParam + } + + txSignature := hex.EncodeToString(request.Signatures[0].Bytes) + elrondTx.Signature = txSignature + + signedTxBytes, err := json.Marshal(elrondTx) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + signedTx := hex.EncodeToString(signedTxBytes) + + return &types.ConstructionCombineResponse{ + SignedTransaction: signedTx, + }, nil +} + +// ConstructionDerive returns a bech32 address from public key bytes +func (cas *constructionAPIService) ConstructionDerive( + _ context.Context, + request *types.ConstructionDeriveRequest, +) (*types.ConstructionDeriveResponse, *types.Error) { + if request.PublicKey.CurveType != types.Edwards25519 { + return nil, ErrUnsupportedCurveType + } + + pubKey := request.PublicKey.Bytes + address, err := cas.elrondProvider.EncodeAddress(pubKey) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + return &types.ConstructionDeriveResponse{ + AccountIdentifier: &types.AccountIdentifier{ + Address: address, + }, + Metadata: nil, + }, nil +} + +// ConstructionHash will calculate transaction hash +func (cas *constructionAPIService) ConstructionHash( + _ context.Context, + request *types.ConstructionHashRequest, +) (*types.TransactionIdentifierResponse, *types.Error) { + elrondTx, err := getTxFromRequest(request.SignedTransaction) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + txHash, err := cas.elrondProvider.ComputeTransactionHash(elrondTx) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + return &types.TransactionIdentifierResponse{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: txHash, + }, + }, nil +} + +// ConstructionSubmit will submit transaction and return hash +func (cas *constructionAPIService) ConstructionSubmit( + _ context.Context, + request *types.ConstructionSubmitRequest, +) (*types.TransactionIdentifierResponse, *types.Error) { + elrondTx, err := getTxFromRequest(request.SignedTransaction) + if err != nil { + return nil, wrapErr(ErrMalformedValue, err) + } + + txHash, err := cas.elrondProvider.SendTx(elrondTx) + if err != nil { + return nil, wrapErr(ErrUnableToSubmitTransaction, err) + } + + return &types.TransactionIdentifierResponse{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: txHash, + }, + }, nil +} diff --git a/rosetta/services/construction_service_test.go b/rosetta/services/construction_service_test.go new file mode 100644 index 00000000..e345ee92 --- /dev/null +++ b/rosetta/services/construction_service_test.go @@ -0,0 +1,452 @@ +package services + +import ( + "context" + "fmt" + "testing" + + "github.com/ElrondNetwork/elrond-proxy-go/config" + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/require" +) + +func TestConstructionAPIService_ConstructionPreprocess(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + elrondProvider := &mocks.ElrondProviderMock{} + + constructionAPIService := NewConstructionAPIService(elrondProvider, cfg, networkCfg) + + senderAddr := "senderAddr" + receiverAddr := "receiverAddr" + value := "123456" + + operations := []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: senderAddr, + }, + Amount: &types.Amount{ + Value: "-" + value, + Currency: cfg.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: receiverAddr, + }, + Amount: &types.Amount{ + Value: value, + Currency: cfg.Currency, + }, + }, + } + + feeMultiplier := 1.1 + maxFee := "1234567" + + gasPrice := uint64(100) + gasLimit := uint64(10000) + dataField := "data" + + response, err := constructionAPIService.ConstructionPreprocess(context.Background(), + &types.ConstructionPreprocessRequest{ + Operations: operations, + MaxFee: []*types.Amount{ + { + Value: maxFee, + Currency: cfg.Currency, + }, + }, + SuggestedFeeMultiplier: &feeMultiplier, + Metadata: objectsMap{ + "gasPrice": gasPrice, + "gasLimit": gasLimit, + "data": dataField, + }, + }, + ) + require.Nil(t, err) + require.Equal(t, map[string]interface{}{ + "receiver": receiverAddr, + "sender": senderAddr, + "gasPrice": gasPrice, + "gasLimit": gasLimit, + "feeMultiplier": feeMultiplier, + "data": dataField, + "value": value, + "maxFee": maxFee, + "type": opTransfer, + }, response.Options) +} + +func TestConstructionAPIService_ConstructionMetadata(t *testing.T) { + t.Parallel() + + senderAddr := "senderAddr" + receiverAddr := "receiverAddr" + value := "123456" + feeMultiplier := 1.1 + maxFee := "1234567" + gasPrice := uint64(100) + gasLimit := uint64(10000) + dataField := "data" + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + nonce := uint64(5) + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + elrondProvider := &mocks.ElrondProviderMock{ + GetAccountCalled: func(address string) (*data.Account, error) { + return &data.Account{ + Address: senderAddr, + Nonce: nonce, + }, nil + }, + } + + constructionAPIService := NewConstructionAPIService(elrondProvider, cfg, networkCfg) + + options := map[string]interface{}{ + "receiver": receiverAddr, + "sender": senderAddr, + "gasPrice": gasPrice, + "gasLimit": gasLimit, + "feeMultiplier": feeMultiplier, + "data": dataField, + "value": value, + "maxFee": maxFee, + "type": opTransfer, + } + response, err := constructionAPIService.ConstructionMetadata(context.Background(), + &types.ConstructionMetadataRequest{ + Options: options, + }, + ) + + expectedSuggestedFee := "1100000" + require.Nil(t, err) + require.Equal(t, expectedSuggestedFee, response.SuggestedFee[0].Value) + require.Equal(t, cfg.Currency, response.SuggestedFee[0].Currency) + + delete(options, "feeMultiplier") + delete(options, "maxFee") + delete(options, "type") + options["chainID"] = networkCfg.ChainID + options["version"] = networkCfg.MinTxVersion + options["data"] = []byte(fmt.Sprintf("%v", dataField)) + options["nonce"] = nonce + options["gasLimit"] = uint64(10000) + options["gasPrice"] = uint64(110) + require.Equal(t, options, response.Metadata) +} + +func TestConstructionAPIService_ConstructionPayloads(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + + nonce := uint64(5) + senderAddr := "senderAddr" + receiverAddr := "receiverAddr" + value := "123456" + gasPrice := uint64(100) + gasLimit := uint64(10000) + dataField := "data" + metadata := map[string]interface{}{ + "nonce": nonce, + "receiver": receiverAddr, + "sender": senderAddr, + "gasPrice": gasPrice, + "gasLimit": gasLimit, + "data": []byte(fmt.Sprintf("%v", dataField)), + "value": value, + "chainID": networkCfg.ChainID, + "version": networkCfg.MinTxVersion, + } + + operations := []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: senderAddr, + }, + Amount: &types.Amount{ + Value: "-" + value, + Currency: cfg.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: receiverAddr, + }, + Amount: &types.Amount{ + Value: value, + Currency: cfg.Currency, + }, + }, + } + + constructionAPIService := NewConstructionAPIService(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + response, err := constructionAPIService.ConstructionPayloads(context.Background(), + &types.ConstructionPayloadsRequest{ + Operations: operations, + Metadata: metadata, + }, + ) + + marshalizedTx := []byte("{\"nonce\":5,\"value\":\"123456\",\"receiver\":\"receiverAddr\",\"sender\":\"senderAddr\",\"gasPrice\":100,\"gasLimit\":10000,\"data\":\"ZGF0YQ==\",\"chainID\":\"local-testnet\",\"version\":1}") + unsignedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + + require.Nil(t, err) + require.Equal(t, unsignedTx, response.UnsignedTransaction) + require.Equal(t, marshalizedTx, response.Payloads[0].Bytes) + require.Equal(t, senderAddr, response.Payloads[0].AccountIdentifier.Address) + require.Equal(t, types.Ed25519, response.Payloads[0].SignatureType) +} + +func TestConstructionAPIService_ConstructionParse(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + constructionAPIService := NewConstructionAPIService(&mocks.ElrondProviderMock{}, cfg, networkCfg) + unsignedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + + senderAddr := "senderAddr" + receiverAddr := "receiverAddr" + value := "123456" + operations := []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: senderAddr, + }, + Amount: &types.Amount{ + Value: "-" + value, + Currency: cfg.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: receiverAddr, + }, + Amount: &types.Amount{ + Value: value, + Currency: cfg.Currency, + }, + }, + } + + response, err := constructionAPIService.ConstructionParse(context.Background(), + &types.ConstructionParseRequest{ + Signed: false, + Transaction: unsignedTx, + }, + ) + require.Nil(t, err) + require.Equal(t, operations, response.Operations) + require.Nil(t, response.AccountIdentifierSigners) + + response, err = constructionAPIService.ConstructionParse(context.Background(), + &types.ConstructionParseRequest{ + Signed: true, + Transaction: unsignedTx, + }, + ) + require.Nil(t, err) + require.Equal(t, operations, response.Operations) + require.NotNil(t, response.AccountIdentifierSigners) +} + +func TestConstructionAPIService_ConstructionCombine(t *testing.T) { + t.Parallel() + + unsignedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + signature := []byte("signature-signature-signature") + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + constructionAPIService := NewConstructionAPIService(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + response, err := constructionAPIService.ConstructionCombine(context.Background(), + &types.ConstructionCombineRequest{ + UnsignedTransaction: unsignedTx, + Signatures: []*types.Signature{ + { + Bytes: signature, + }, + }, + }, + ) + + signedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c227369676e6174757265223a2237333639363736653631373437353732363532643733363936373665363137343735373236353264373336393637366536313734373537323635222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + require.Nil(t, err) + require.Equal(t, signedTx, response.SignedTransaction) +} + +func TestConstructionAPIService_ConstructionDerive(t *testing.T) { + t.Parallel() + + encodedAddress := "erd12312321321321123321321" + elrondProvider := &mocks.ElrondProviderMock{ + EncodeAddressCalled: func(address []byte) (string, error) { + return encodedAddress, nil + }, + } + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + constructionAPIService := NewConstructionAPIService(elrondProvider, cfg, networkCfg) + + response, err := constructionAPIService.ConstructionDerive(context.Background(), + &types.ConstructionDeriveRequest{ + PublicKey: &types.PublicKey{ + Bytes: []byte("blablabla"), + CurveType: types.Edwards25519, + }, + }, + ) + require.Nil(t, err) + require.Equal(t, encodedAddress, response.AccountIdentifier.Address) +} + +func TestConstructionAPIService_ConstructionHash(t *testing.T) { + t.Parallel() + + txHash := "hash-hash-hash" + signedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c227369676e6174757265223a2237333639363736653631373437353732363532643733363936373665363137343735373236353264373336393637366536313734373537323635222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + elrondProvider := &mocks.ElrondProviderMock{ + ComputeTransactionHashCalled: func(tx *data.Transaction) (string, error) { + return txHash, nil + }, + } + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + constructionAPIService := NewConstructionAPIService(elrondProvider, cfg, networkCfg) + + response, err := constructionAPIService.ConstructionHash(context.Background(), + &types.ConstructionHashRequest{ + SignedTransaction: signedTx, + }, + ) + require.Nil(t, err) + require.Equal(t, txHash, response.TransactionIdentifier.Hash) +} + +func TestConstructionAPIService_ConstructionSubmit(t *testing.T) { + t.Parallel() + + txHash := "hash-hash-hash" + signedTx := "7b226e6f6e6365223a352c2276616c7565223a22313233343536222c227265636569766572223a22726563656976657241646472222c2273656e646572223a2273656e64657241646472222c226761735072696365223a3130302c226761734c696d6974223a31303030302c2264617461223a225a47463059513d3d222c227369676e6174757265223a2237333639363736653631373437353732363532643733363936373665363137343735373236353264373336393637366536313734373537323635222c22636861696e4944223a226c6f63616c2d746573746e6574222c2276657273696f6e223a317d" + elrondProvider := &mocks.ElrondProviderMock{ + SendTxCalled: func(tx *data.Transaction) (string, error) { + return txHash, nil + }, + } + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + ChainID: "local-testnet", + MinTxVersion: 1, + } + cfg := configuration.LoadConfiguration(networkCfg, &config.Config{}) + constructionAPIService := NewConstructionAPIService(elrondProvider, cfg, networkCfg) + + response, err := constructionAPIService.ConstructionSubmit(context.Background(), + &types.ConstructionSubmitRequest{ + SignedTransaction: signedTx, + }, + ) + require.Nil(t, err) + require.Equal(t, txHash, response.TransactionIdentifier.Hash) +} diff --git a/rosetta/services/errors.go b/rosetta/services/errors.go new file mode 100644 index 00000000..904e804b --- /dev/null +++ b/rosetta/services/errors.go @@ -0,0 +1,143 @@ +package services + +import "github.com/coinbase/rosetta-sdk-go/types" + +var ( + ErrUnableToGetChainID = &types.Error{ + Code: 1, + Message: "unable to get chain ID", + Retriable: true, + } + + ErrUnableToGetAccount = &types.Error{ + Code: 2, + Message: "unable to get account", + Retriable: true, + } + + ErrInvalidAccountAddress = &types.Error{ + Code: 3, + Message: "invalid account address", + Retriable: false, + } + + ErrUnableToGetBlock = &types.Error{ + Code: 4, + Message: "unable to get block", + Retriable: true, + } + + ErrNotImplemented = &types.Error{ + Code: 5, + Message: "operation not implemented", + Retriable: false, + } + + ErrUnableToSubmitTransaction = &types.Error{ + Code: 6, + Message: "unable to submit transaction", + Retriable: false, + } + + ErrMalformedValue = &types.Error{ + Code: 7, + Message: "malformed value", + Retriable: false, + } + + ErrUnableToGetNodeStatus = &types.Error{ + Code: 8, + Message: "unable to get node status", + Retriable: true, + } + + ErrUnableToGetClientVersion = &types.Error{ + Code: 9, + Message: "unable to get client version", + Retriable: true, + } + ErrMustQueryByIndexOrByHash = &types.Error{ + Code: 10, + Message: "must query block by index or by hash", + Retriable: false, + } + ErrConstructionCheck = &types.Error{ + Code: 11, + Message: "operation construction check error", + Retriable: false, + } + ErrUnableToGetNetworkConfig = &types.Error{ + Code: 12, + Message: "unable to get network config", + Retriable: true, + } + ErrInvalidInputParam = &types.Error{ + Code: 13, + Message: "Invalid input param: ", + Retriable: false, + } + ErrUnsupportedCurveType = &types.Error{ + Code: 14, + Message: "unsupported curve type", + Retriable: false, + } + ErrInsufficientGasLimit = &types.Error{ + Code: 15, + Message: "insufficient gas limit", + Retriable: false, + } + ErrGasPriceTooLow = &types.Error{ + Code: 16, + Message: "gas price is to low", + Retriable: false, + } + ErrTransactionIsNotInPool = &types.Error{ + Code: 17, + Message: "transaction is not in pool", + Retriable: false, + } + ErrCannotParsePoolTransaction = &types.Error{ + Code: 18, + Message: "cannot parse pool transaction", + Retriable: false, + } + + Errors = []*types.Error{ + ErrUnableToGetChainID, + ErrUnableToGetAccount, + ErrInvalidAccountAddress, + ErrUnableToGetBlock, + ErrNotImplemented, + ErrUnableToSubmitTransaction, + ErrMalformedValue, + ErrUnableToGetNodeStatus, + ErrUnableToGetClientVersion, + ErrMustQueryByIndexOrByHash, + ErrConstructionCheck, + ErrUnableToGetNetworkConfig, + ErrUnsupportedCurveType, + ErrInsufficientGasLimit, + ErrGasPriceTooLow, + ErrTransactionIsNotInPool, + ErrCannotParsePoolTransaction, + ErrInvalidInputParam, + } +) + +// wrapErr adds details to the types.Error provided. We use a function +// to do this so that we don't accidentally overwrite the standard +// errors. +func wrapErr(rErr *types.Error, err error) *types.Error { + newErr := &types.Error{ + Code: rErr.Code, + Message: rErr.Message, + Retriable: rErr.Retriable, + } + if err != nil { + newErr.Details = map[string]interface{}{ + "context": err.Error(), + } + } + + return newErr +} diff --git a/rosetta/services/mempool_service.go b/rosetta/services/mempool_service.go new file mode 100644 index 00000000..0f51fd4b --- /dev/null +++ b/rosetta/services/mempool_service.go @@ -0,0 +1,53 @@ +package services + +import ( + "context" + + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +type mempoolAPIService struct { + elrondProvider provider.ElrondProviderHandler + txsParser *transactionsParser +} + +// NewMempoolApiService will create a new instance of mempoolAPIService +func NewMempoolApiService( + elrondProvider provider.ElrondProviderHandler, + cfg *configuration.Configuration, + networkConfig *provider.NetworkConfig, +) server.MempoolAPIServicer { + return &mempoolAPIService{ + elrondProvider: elrondProvider, + txsParser: newTransactionParser(elrondProvider, cfg, networkConfig), + } +} + +// Mempool is not implemented yet +func (mas *mempoolAPIService) Mempool(context.Context, *types.NetworkRequest) (*types.MempoolResponse, *types.Error) { + return nil, ErrNotImplemented +} + +// MempoolTransaction will return operations for a transaction that is in pool +func (mas *mempoolAPIService) MempoolTransaction( + _ context.Context, + request *types.MempoolTransactionRequest, +) (*types.MempoolTransactionResponse, *types.Error) { + tx, ok := mas.elrondProvider.GetTransactionByHashFromPool(request.TransactionIdentifier.Hash) + if !ok { + return nil, ErrTransactionIsNotInPool + } + + rosettaTx, ok := mas.txsParser.parseTx(tx, true) + if !ok { + return nil, ErrCannotParsePoolTransaction + } + + return &types.MempoolTransactionResponse{ + Transaction: rosettaTx, + }, nil + +} diff --git a/rosetta/services/mempool_service_test.go b/rosetta/services/mempool_service_test.go new file mode 100644 index 00000000..f2a3c7f1 --- /dev/null +++ b/rosetta/services/mempool_service_test.go @@ -0,0 +1,113 @@ +package services + +import ( + "context" + "testing" + + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/require" +) + +func TestMempoolAPIService_MempoolTransactionCannotFindTxInPool(t *testing.T) { + t.Parallel() + + elrondProviderMock := &mocks.ElrondProviderMock{ + GetTransactionByHashFromPoolCalled: func(txHash string) (*data.FullTransaction, bool) { + return nil, false + }, + } + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + mempoolApiService := NewMempoolApiService(elrondProviderMock, cfg, networkCfg) + + txHash := "hash-hash-hash" + txResponse, err := mempoolApiService.MempoolTransaction(context.Background(), &types.MempoolTransactionRequest{ + NetworkIdentifier: nil, + TransactionIdentifier: &types.TransactionIdentifier{Hash: txHash}, + }) + require.Equal(t, ErrTransactionIsNotInPool, err) + require.Nil(t, txResponse) +} + +func TestMempoolAPIService_MempoolTransaction(t *testing.T) { + t.Parallel() + + txHash := "hash-hash-hash" + fullTx := &data.FullTransaction{ + Hash: txHash, + Type: string(transaction.TxTypeNormal), + Receiver: "erd1uml89f3lqqfxan67dnnlytd0r3mz3v684zxdhqq60gs5u7qa9yjqa5dgqp", + Sender: "erd18f33a94auxr4v8v23wu8gwv7mzf408jsskktvj4lcmcrv4v5jmqs5x3kdn", + Value: "1234", + GasLimit: 100, + GasPrice: 10, + } + elrondProviderMock := &mocks.ElrondProviderMock{ + GetTransactionByHashFromPoolCalled: func(txHash string) (*data.FullTransaction, bool) { + return fullTx, true + }, + } + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + mempoolApiService := NewMempoolApiService(elrondProviderMock, cfg, networkCfg) + + expectedRosettaTx := &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{Hash: txHash}, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: fullTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + fullTx.Value, + Currency: nil, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: fullTx.Receiver, + }, + Amount: &types.Amount{ + Value: fullTx.Value, + Currency: nil, + }, + }, + }, + } + + txResponse, err := mempoolApiService.MempoolTransaction(context.Background(), &types.MempoolTransactionRequest{ + NetworkIdentifier: nil, + TransactionIdentifier: &types.TransactionIdentifier{Hash: txHash}, + }) + require.Nil(t, err) + require.Equal(t, expectedRosettaTx, txResponse.Transaction) +} diff --git a/rosetta/services/network_service.go b/rosetta/services/network_service.go new file mode 100644 index 00000000..a187b911 --- /dev/null +++ b/rosetta/services/network_service.go @@ -0,0 +1,113 @@ +package services + +import ( + "context" + + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/server" + "github.com/coinbase/rosetta-sdk-go/types" +) + +// NetworkAPIService implements the server.NetworkAPIServicer interface. +type networkAPIService struct { + elrondProvider provider.ElrondProviderHandler + config *configuration.Configuration +} + +// NewNetworkAPIService creates a new instance of a NetworkAPIService. +func NewNetworkAPIService(elrondProvider provider.ElrondProviderHandler, cfg *configuration.Configuration) server.NetworkAPIServicer { + return &networkAPIService{ + elrondProvider: elrondProvider, + config: cfg, + } +} + +// NetworkList implements the /network/list endpoint +func (nas *networkAPIService) NetworkList( + _ context.Context, + _ *types.MetadataRequest, +) (*types.NetworkListResponse, *types.Error) { + return &types.NetworkListResponse{ + NetworkIdentifiers: []*types.NetworkIdentifier{ + nas.config.Network, + }, + }, nil +} + +// NetworkStatus implements the /network/status endpoint. +func (nas *networkAPIService) NetworkStatus( + _ context.Context, + _ *types.NetworkRequest, +) (*types.NetworkStatusResponse, *types.Error) { + latestBlockData, err := nas.elrondProvider.GetLatestBlockData() + if err != nil { + return nil, wrapErr(ErrUnableToGetNodeStatus, err) + } + + networkStatusResponse := &types.NetworkStatusResponse{ + CurrentBlockIdentifier: &types.BlockIdentifier{ + Index: int64(latestBlockData.Nonce), + Hash: latestBlockData.Hash, + }, + CurrentBlockTimestamp: latestBlockData.Timestamp, + GenesisBlockIdentifier: nas.config.GenesisBlockIdentifier, + Peers: nas.config.Peers, + } + + oldBlock, err := nas.getOldestBlock(latestBlockData.Nonce) + if err == nil { + networkStatusResponse.OldestBlockIdentifier = &types.BlockIdentifier{ + Index: int64(oldBlock.Nonce), + Hash: oldBlock.Hash, + } + } + + return networkStatusResponse, nil +} + +func (nas *networkAPIService) getOldestBlock(latestBlockNonce uint64) (*provider.BlockData, error) { + oldestBlockNonce := uint64(1) + + if latestBlockNonce > NumBlocksToGet { + oldestBlockNonce = latestBlockNonce - NumBlocksToGet + } + + block, err := nas.elrondProvider.GetBlockByNonce(int64(oldestBlockNonce)) + if err != nil { + return nil, err + } + + return &provider.BlockData{ + Nonce: block.Nonce, + Hash: block.Hash, + }, nil + +} + +// NetworkOptions implements the /network/options endpoint. +func (nas *networkAPIService) NetworkOptions( + _ context.Context, + _ *types.NetworkRequest, +) (*types.NetworkOptionsResponse, *types.Error) { + return &types.NetworkOptionsResponse{ + Version: &types.Version{ + RosettaVersion: RosettaVersion, + NodeVersion: NodeVersion, + }, + Allow: &types.Allow{ + OperationStatuses: []*types.OperationStatus{ + { + Status: OpStatusSuccess, + Successful: true, + }, + { + Status: OpStatusFailed, + Successful: false, + }, + }, + OperationTypes: SupportedOperationTypes, + Errors: Errors, + }, + }, nil +} diff --git a/rosetta/services/network_service_test.go b/rosetta/services/network_service_test.go new file mode 100644 index 00000000..db91ab05 --- /dev/null +++ b/rosetta/services/network_service_test.go @@ -0,0 +1,122 @@ +package services + +import ( + "context" + "testing" + + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/assert" +) + +func TestNetworkAPIService_NetworkList(t *testing.T) { + t.Parallel() + + elrondProviderMock := &mocks.ElrondProviderMock{} + cfg := &configuration.Configuration{ + Network: &types.NetworkIdentifier{ + Blockchain: configuration.BlockchainName, + Network: "local_network", + }, + } + + networkAPIService := NewNetworkAPIService(elrondProviderMock, cfg) + + networkListResponse, err := networkAPIService.NetworkList(context.Background(), nil) + assert.Nil(t, err) + assert.Equal(t, []*types.NetworkIdentifier{{ + Blockchain: configuration.BlockchainName, + Network: "local_network", + }}, networkListResponse.NetworkIdentifiers) +} + +func TestNetworkAPIService_NetworkOptions(t *testing.T) { + t.Parallel() + + elrondProviderMock := &mocks.ElrondProviderMock{} + cfg := &configuration.Configuration{ + Network: &types.NetworkIdentifier{ + Blockchain: configuration.BlockchainName, + Network: "local_network", + }, + } + networkAPIService := NewNetworkAPIService(elrondProviderMock, cfg) + + networkOptions, err := networkAPIService.NetworkOptions(context.Background(), nil) + assert.Nil(t, err) + assert.Equal(t, &types.NetworkOptionsResponse{ + Version: &types.Version{ + RosettaVersion: RosettaVersion, + NodeVersion: NodeVersion, + }, + Allow: &types.Allow{ + OperationStatuses: []*types.OperationStatus{ + { + Status: OpStatusSuccess, + Successful: true, + }, + { + Status: OpStatusFailed, + Successful: false, + }, + }, + OperationTypes: SupportedOperationTypes, + Errors: Errors, + }, + }, networkOptions) +} + +func TestNetworkAPIService_NetworkStatus(t *testing.T) { + t.Parallel() + + latestBlockNonce := int64(1000) + latestBlockHash := "hash" + oldestBlockNonce := int64(800) + oldestBlockHash := "old" + elrondProviderMock := &mocks.ElrondProviderMock{ + GetLatestBlockDataCalled: func() (*provider.BlockData, error) { + return &provider.BlockData{ + Hash: latestBlockHash, + Nonce: uint64(latestBlockNonce), + }, nil + }, + GetBlockByNonceCalled: func(nonce int64) (*data.Hyperblock, error) { + return &data.Hyperblock{ + Hash: oldestBlockHash, + Nonce: uint64(oldestBlockNonce), + }, nil + }, + } + cfg := &configuration.Configuration{ + GenesisBlockIdentifier: &types.BlockIdentifier{ + Index: 1, + Hash: configuration.GenesisBlockHashMainnet, + }, + Peers: []*types.Peer{ + { + PeerID: "bla-bla-bla", + }, + }, + } + networkAPIService := NewNetworkAPIService(elrondProviderMock, cfg) + + networkStatusResponse, err := networkAPIService.NetworkStatus(context.Background(), nil) + assert.Nil(t, err) + assert.Equal(t, &types.NetworkStatusResponse{ + CurrentBlockIdentifier: &types.BlockIdentifier{ + Index: latestBlockNonce, + Hash: latestBlockHash, + }, + CurrentBlockTimestamp: 0, + GenesisBlockIdentifier: cfg.GenesisBlockIdentifier, + OldestBlockIdentifier: &types.BlockIdentifier{ + Index: oldestBlockNonce, + Hash: oldestBlockHash, + }, + SyncStatus: nil, + Peers: cfg.Peers, + }, networkStatusResponse) +} diff --git a/rosetta/services/transaction_fee.go b/rosetta/services/transaction_fee.go new file mode 100644 index 00000000..2096376c --- /dev/null +++ b/rosetta/services/transaction_fee.go @@ -0,0 +1,124 @@ +package services + +import ( + "fmt" + "math/big" + + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" +) + +func computeSuggestedFeeAndGas(txType string, options objectsMap, networkConfig *provider.NetworkConfig) (*big.Int, uint64, uint64, *types.Error) { + var gasLimit, gasPrice uint64 + + if gasLimitI, ok := options["gasLimit"]; ok { + gasLimit = getUint64Value(gasLimitI) + + err := checkProvidedGasLimit(gasLimit, txType, options, networkConfig) + if err != nil { + return nil, 0, 0, err + } + + } else { + // if gas limit is not provided, we estimate it + estimatedGasLimit, err := estimateGasLimit(txType, networkConfig, options) + if err != nil { + return nil, 0, 0, err + } + + gasLimit = estimatedGasLimit + } + + if gasPriceI, ok := options["gasPrice"]; ok { + gasPrice = getUint64Value(gasPriceI) + + if gasPrice < networkConfig.MinGasPrice { + return nil, 0, 0, ErrGasPriceTooLow + } + + } else { + // if gas price is not provided, we set it to minGasPrice + gasPrice = networkConfig.MinGasPrice + } + + suggestedFee := big.NewInt(0).Mul( + big.NewInt(0).SetUint64(gasPrice), + big.NewInt(0).SetUint64(gasLimit), + ) + + suggestedFee, gasPrice = adjustTxFeeWithFeeMultiplier(suggestedFee, gasPrice, options, networkConfig.MinGasPrice) + + return suggestedFee, gasPrice, gasLimit, nil +} + +func adjustTxFeeWithFeeMultiplier( + txFee *big.Int, gasPrice uint64, options objectsMap, minGasPrice uint64, +) (*big.Int, uint64) { + feeMultiplierI, ok := options["feeMultiplier"] + if !ok { + return txFee, gasPrice + } + + feeMultiplier, ok := feeMultiplierI.(float64) + if !ok { + return txFee, gasPrice + } + + feeMultiplierBig := big.NewFloat(feeMultiplier) + bigVal, ok := big.NewFloat(0).SetString(txFee.String()) + if !ok { + return txFee, gasPrice + } + + bigVal.Mul(bigVal, feeMultiplierBig) + + result := new(big.Int) + bigVal.Int(result) + + gasPrice = uint64(feeMultiplier * float64(gasPrice)) + if gasPrice < minGasPrice { + gasPrice = minGasPrice + } + + return result, gasPrice +} + +func estimateGasLimit(operationType string, networkConfig *provider.NetworkConfig, options objectsMap) (uint64, *types.Error) { + gasForDataField := uint64(0) + if dataFieldI, ok := options["data"]; ok { + dataField := fmt.Sprintf("%v", dataFieldI) + gasForDataField = networkConfig.GasPerDataByte * uint64(len(dataField)) + } + + switch operationType { + case opTransfer: + return networkConfig.MinGasLimit + gasForDataField, nil + default: + // we do not support this yet other operation types, but we might support it in the future + return 0, ErrNotImplemented + } +} + +func checkProvidedGasLimit(providedGasLimit uint64, txType string, options objectsMap, networkConfig *provider.NetworkConfig) *types.Error { + estimatedGasLimit, err := estimateGasLimit(txType, networkConfig, options) + if err != nil { + return err + } + + if providedGasLimit < estimatedGasLimit { + return ErrInsufficientGasLimit + } + + return nil +} + +func getUint64Value(obj interface{}) uint64 { + if value, ok := obj.(uint64); ok { + return value + } + if value, ok := obj.(float64); ok { + return uint64(value) + } + + return 0 +} diff --git a/rosetta/services/transaction_fee_test.go b/rosetta/services/transaction_fee_test.go new file mode 100644 index 00000000..dba7e064 --- /dev/null +++ b/rosetta/services/transaction_fee_test.go @@ -0,0 +1,158 @@ +package services + +import ( + "math/big" + "testing" + + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/stretchr/testify/assert" +) + +func TestEstimateGasLimit(t *testing.T) { + t.Parallel() + + minGasLimit := uint64(1000) + gasPerDataByte := uint64(100) + networkConfig := &provider.NetworkConfig{ + GasPerDataByte: gasPerDataByte, + MinGasLimit: minGasLimit, + } + + dataField := "transaction-data" + options := objectsMap{ + "data": dataField, + } + + expectedGasLimit := minGasLimit + uint64(len(dataField))*gasPerDataByte + + gasLimit, err := estimateGasLimit(opTransfer, networkConfig, options) + assert.Nil(t, err) + assert.Equal(t, expectedGasLimit, gasLimit) + + gasLimit, err = estimateGasLimit(opTransfer, networkConfig, nil) + assert.Nil(t, err) + assert.Equal(t, minGasLimit, gasLimit) + + // unsupported operation type you cannot estimate gasLimit for a reward operation + // reward operation can be generated only by the network not by a user + gasLimit, err = estimateGasLimit(opReward, networkConfig, nil) + assert.Equal(t, ErrNotImplemented, err) + assert.Equal(t, uint64(0), gasLimit) +} + +func TestProvidedGasLimit(t *testing.T) { + t.Parallel() + + minGasLimit := uint64(1000) + gasPerDataByte := uint64(100) + networkConfig := &provider.NetworkConfig{ + GasPerDataByte: gasPerDataByte, + MinGasLimit: minGasLimit, + } + + dataField := "transaction-data" + options := objectsMap{ + "data": dataField, + } + + err := checkProvidedGasLimit(uint64(900), opTransfer, options, networkConfig) + assert.Equal(t, ErrInsufficientGasLimit, err) + + err = checkProvidedGasLimit(uint64(900), opReward, options, networkConfig) + assert.Equal(t, ErrNotImplemented, err) + + err = checkProvidedGasLimit(uint64(9000), opTransfer, options, networkConfig) + assert.Nil(t, err) +} + +func TestAdjustTxFeeWithFeeMultiplier(t *testing.T) { + t.Parallel() + + options := objectsMap{ + "feeMultiplier": 1.1, + } + + expectedGasPrice := uint64(1100) + expectedFee := "1100" + suggestedFee := big.NewInt(1000) + + suggestedFeeResult, gasPriceResult := adjustTxFeeWithFeeMultiplier(suggestedFee, 1000, options, 1000) + assert.Equal(t, expectedFee, suggestedFeeResult.String()) + assert.Equal(t, expectedGasPrice, gasPriceResult) + + expectedGasPrice = uint64(1000) + expectedFee = "1000" + suggestedFeeResult, gasPriceResult = adjustTxFeeWithFeeMultiplier(suggestedFee, 1000, make(objectsMap), 1000) + assert.Equal(t, expectedFee, suggestedFeeResult.String()) + assert.Equal(t, expectedGasPrice, gasPriceResult) +} + +func TestComputeSuggestedFeeAndGas(t *testing.T) { + t.Parallel() + + minGasLimit := uint64(1000) + minGasPrice := uint64(10) + gasPerDataByte := uint64(100) + networkConfig := &provider.NetworkConfig{ + GasPerDataByte: gasPerDataByte, + MinGasLimit: minGasLimit, + MinGasPrice: minGasPrice, + } + + providedGasPrice := uint64(10) + options := objectsMap{ + "gasPrice": providedGasPrice, + } + + suggestedFee, gasPrice, gasLimit, err := computeSuggestedFeeAndGas(opTransfer, options, networkConfig) + assert.Nil(t, err) + assert.Equal(t, minGasLimit, gasLimit) + assert.Equal(t, big.NewInt(10000), suggestedFee) + assert.Equal(t, providedGasPrice, gasPrice) + + // err provided gas price is too low + options["gasPrice"] = 1 + _, _, _, err = computeSuggestedFeeAndGas(opTransfer, options, networkConfig) + assert.Equal(t, ErrGasPriceTooLow, err) + + // err provided gas limit is too low + options["gasPrice"] = minGasPrice + options["gasLimit"] = 1 + _, _, _, err = computeSuggestedFeeAndGas(opTransfer, options, networkConfig) + assert.Equal(t, ErrInsufficientGasLimit, err) + + delete(options, "gasLimit") + options["gasPrice"] = minGasPrice + _, _, _, err = computeSuggestedFeeAndGas(opReward, options, networkConfig) + assert.Equal(t, ErrNotImplemented, err) + + //check with fee multiplier + delete(options, "gasPrice") + delete(options, "gasLimit") + options["feeMultiplier"] = 1.1 + expectedSuggestedFee := big.NewInt(11000) + expectedGasPrice := uint64(11) + suggestedFee, gasPrice, gasLimit, err = computeSuggestedFeeAndGas(opTransfer, options, networkConfig) + assert.Nil(t, err) + assert.Equal(t, minGasLimit, gasLimit) + assert.Equal(t, expectedSuggestedFee, suggestedFee) + assert.Equal(t, expectedGasPrice, gasPrice) +} + +func TestAdjustTxFeeWithFeeMultiplier_FeeMultiplierLessThanOne(t *testing.T) { + t.Parallel() + + options := objectsMap{ + "feeMultiplier": 0.5, + } + + expectedFee := "500" + suggestedFee := big.NewInt(1000) + gasPrice := uint64(1000) + minGasPrice := uint64(900) + + suggestedFeeResult, gasPriceResult := adjustTxFeeWithFeeMultiplier(suggestedFee, gasPrice, options, minGasPrice) + assert.Equal(t, expectedFee, suggestedFeeResult.String()) + assert.Equal(t, minGasPrice, gasPriceResult) + +} diff --git a/rosetta/services/transaction_parser.go b/rosetta/services/transaction_parser.go new file mode 100644 index 00000000..4ca60316 --- /dev/null +++ b/rosetta/services/transaction_parser.go @@ -0,0 +1,316 @@ +package services + +import ( + "math/big" + + "github.com/ElrondNetwork/elrond-go/core" + "github.com/ElrondNetwork/elrond-go/data/transaction" + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" +) + +type transactionsParser struct { + config *configuration.Configuration + networkConfig *provider.NetworkConfig + elrondProvider provider.ElrondProviderHandler +} + +func newTransactionParser( + provider provider.ElrondProviderHandler, + cfg *configuration.Configuration, + networkConfig *provider.NetworkConfig, +) *transactionsParser { + return &transactionsParser{ + config: cfg, + networkConfig: networkConfig, + elrondProvider: provider, + } +} + +func (tp *transactionsParser) parseTxsFromHyperBlock(hyperBlock *data.Hyperblock) []*types.Transaction { + txs := make([]*types.Transaction, 0) + for _, eTx := range hyperBlock.Transactions { + tx, ok := tp.parseTx(eTx, false) + if !ok { + continue + } + txs = append(txs, tx) + } + + return txs +} + +func (tp *transactionsParser) parseTx(eTx *data.FullTransaction, isInPool bool) (*types.Transaction, bool) { + switch eTx.Type { + case string(transaction.TxTypeNormal): + return tp.createRosettaTxFromMoveBalance(eTx, isInPool), true + case string(transaction.TxTypeReward): + return tp.createRosettaTxFromReward(eTx), true + case string(transaction.TxTypeUnsigned): + return tp.createRosettaTxFromUnsignedTx(eTx) + case string(transaction.TxTypeInvalid): + return tp.createRosettaTxFromInvalidTx(eTx), true + default: + return nil, false + } +} + +func (tp *transactionsParser) createRosettaTxFromUnsignedTx(eTx *data.FullTransaction) (*types.Transaction, bool) { + // TODO check if we have a SCR that calls another contract + if eTx.Value == "0" { + return nil, false + } + + switch { + case eTx.GasLimit != 0 && eTx.Nonce > 0: + // we have a SCR with gas refund + return tp.createRosettaTxWithGasRefund(eTx) + case eTx.Sender != eTx.Receiver: + // we have a SCR with send funds + return tp.createRosettaTxUnsignedTxSendFunds(eTx) + default: + return nil, false + } +} + +func (tp *transactionsParser) createRosettaTxWithGasRefund(eTx *data.FullTransaction) (*types.Transaction, bool) { + return &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: eTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Receiver, + }, + Amount: &types.Amount{ + Value: eTx.Value, + Currency: tp.config.Currency, + }, + }, + }, + }, true +} + +func (tp *transactionsParser) createRosettaTxUnsignedTxSendFunds( + eTx *data.FullTransaction, +) (*types.Transaction, bool) { + return &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: eTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + eTx.Value, + Currency: tp.config.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Receiver, + }, + Amount: &types.Amount{ + Value: eTx.Value, + Currency: tp.config.Currency, + }, + }, + }, + }, true +} + +func (tp *transactionsParser) createRosettaTxFromReward(eTx *data.FullTransaction) *types.Transaction { + return &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: eTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opReward, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Receiver, + }, + Amount: &types.Amount{ + Value: eTx.Value, + Currency: tp.config.Currency, + }, + }, + }, + } +} + +func (tp *transactionsParser) createRosettaTxFromMoveBalance(eTx *data.FullTransaction, isInPool bool) *types.Transaction { + tx := &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: eTx.Hash, + }, + } + + operations := make([]*types.Operation, 0) + + // check if transaction has value + if eTx.Value != "0" { + operations = append(operations, &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + eTx.Value, + Currency: tp.config.Currency, + }, + }) + + operations = append(operations, &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Receiver, + }, + Amount: &types.Amount{ + Value: eTx.Value, + Currency: tp.config.Currency, + }, + }) + } + + // check if transaction has fee and transaction is not in pool + if eTx.GasLimit != 0 && !isInPool { + operations = append(operations, &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 2, + }, + Type: opFee, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tp.computeTxFee(eTx), + Currency: tp.config.Currency, + }, + }) + } + + if len(operations) != 0 { + tx.Operations = operations + } + + return tx +} + +func (tp *transactionsParser) createOperationsFromPreparedTx(tx *data.Transaction) []*types.Operation { + operations := make([]*types.Operation, 0) + + operations = append(operations, &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: tx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tx.Value, + Currency: tp.config.Currency, + }, + }) + + operations = append(operations, &types.Operation{ + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: tx.Receiver, + }, + Amount: &types.Amount{ + Value: tx.Value, + Currency: tp.config.Currency, + }, + }) + + return operations +} + +func (tp *transactionsParser) createRosettaTxFromInvalidTx(eTx *data.FullTransaction) *types.Transaction { + return &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: eTx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opInvalid, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: eTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tp.computeTxFee(eTx), + Currency: tp.config.Currency, + }, + }, + }, + } +} + +func (tp *transactionsParser) computeTxFee(eTx *data.FullTransaction) string { + // errors should never appear because transaction is included in a block and receiver address is a valid address + decodedAddr, _ := tp.elrondProvider.DecodeAddress(eTx.Receiver) + + if core.IsSmartContractAddress(decodedAddr) { + // we have a smart contract call + fee := big.NewInt(0) + fee.Mul(big.NewInt(0).SetUint64(eTx.GasPrice), big.NewInt(0).SetUint64(eTx.GasLimit)) + + return fee.String() + } + + gasPrice := eTx.GasPrice + gasLimit := tp.networkConfig.MinGasLimit + uint64(len(eTx.Data))*tp.networkConfig.GasPerDataByte + + fee := big.NewInt(0).SetUint64(gasPrice) + fee.Mul(fee, big.NewInt(0).SetUint64(gasLimit)) + + return fee.String() +} diff --git a/rosetta/services/transaction_parser_test.go b/rosetta/services/transaction_parser_test.go new file mode 100644 index 00000000..820af52c --- /dev/null +++ b/rosetta/services/transaction_parser_test.go @@ -0,0 +1,193 @@ +package services + +import ( + "testing" + + "github.com/ElrondNetwork/elrond-proxy-go/data" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/configuration" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/mocks" + "github.com/ElrondNetwork/elrond-proxy-go/rosetta/provider" + "github.com/coinbase/rosetta-sdk-go/types" + "github.com/stretchr/testify/assert" +) + +func TestCreateRosettaTxFromUnsignedTxSendFunds(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + tp := newTransactionParser(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + tx := &data.FullTransaction{ + Hash: "hash-hash", + Receiver: "receiverAddress", + Sender: "senderAddress", + Value: "1234", + } + + rosettaTx, ok := tp.createRosettaTxFromUnsignedTx(tx) + assert.True(t, ok) + assert.Equal(t, &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: tx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: tx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + tx.Value, + Currency: tp.config.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: tx.Receiver, + }, + Amount: &types.Amount{ + Value: tx.Value, + Currency: tp.config.Currency, + }, + }, + }, + }, rosettaTx) +} + +func TestCreateRosettaTxFromUnsignedTxWithoutValueShouldBeIgnored(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + tp := newTransactionParser(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + tx := &data.FullTransaction{ + Hash: "hash-hash", + Receiver: "receiverAddress", + GasLimit: 1000, + Value: "0", + } + + rosettaTx, ok := tp.createRosettaTxFromUnsignedTx(tx) + assert.False(t, ok) + assert.Nil(t, rosettaTx) +} + +func TestCreateRosettaTxFromUnsignedTxRefundGas(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + tp := newTransactionParser(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + tx := &data.FullTransaction{ + Hash: "hash-hash", + Receiver: "receiverAddress", + GasLimit: 1000, + Value: "1234", + Nonce: 1, + } + + rosettaTx, ok := tp.createRosettaTxFromUnsignedTx(tx) + assert.True(t, ok) + assert.Equal(t, &types.Transaction{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: tx.Hash, + }, + Operations: []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opScResult, + Status: OpStatusSuccess, + Account: &types.AccountIdentifier{ + Address: tx.Receiver, + }, + Amount: &types.Amount{ + Value: tx.Value, + Currency: tp.config.Currency, + }, + }, + }, + }, rosettaTx) +} + +func TestCreateOperationsFromPreparedTx(t *testing.T) { + t.Parallel() + + networkCfg := &provider.NetworkConfig{ + GasPerDataByte: 1, + ClientVersion: "", + MinGasPrice: 10, + MinGasLimit: 100, + } + cfg := &configuration.Configuration{} + tp := newTransactionParser(&mocks.ElrondProviderMock{}, cfg, networkCfg) + + preparedTx := &data.Transaction{ + Value: "12345", + Receiver: "receiver", + Sender: "sender", + } + + expectedOperations := []*types.Operation{ + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 0, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: preparedTx.Sender, + }, + Amount: &types.Amount{ + Value: "-" + preparedTx.Value, + Currency: tp.config.Currency, + }, + }, + { + OperationIdentifier: &types.OperationIdentifier{ + Index: 1, + }, + RelatedOperations: []*types.OperationIdentifier{ + {Index: 0}, + }, + Type: opTransfer, + Account: &types.AccountIdentifier{ + Address: preparedTx.Receiver, + }, + Amount: &types.Amount{ + Value: preparedTx.Value, + Currency: tp.config.Currency, + }, + }, + } + + operations := tp.createOperationsFromPreparedTx(preparedTx) + assert.Equal(t, expectedOperations, operations) +} diff --git a/testing/testHttpServer.go b/testing/testHttpServer.go index 1017decc..609443e6 100644 --- a/testing/testHttpServer.go +++ b/testing/testHttpServer.go @@ -92,6 +92,11 @@ func (ths *TestHttpServer) processRequest(rw http.ResponseWriter, req *http.Requ return } + if strings.Contains(req.URL.Path, "network/economics") { + ths.processRequestGetEconomicsMetrics(rw, req) + return + } + if strings.Contains(req.URL.Path, "/cost") { ths.processRequestGetTxCost(rw, req) return @@ -195,6 +200,31 @@ func (ths *TestHttpServer) processRequestGetNetworkMetrics(rw http.ResponseWrite log.LogIfError(err) } +func (ths *TestHttpServer) processRequestGetEconomicsMetrics(rw http.ResponseWriter, _ *http.Request) { + responsStatus := map[string]interface{}{ + "erd_dev_rewards": "0", + "erd_inflation": "120", + "erd_epoch_number": 4, + "erd_total_fees": "3500000000", + "erd_epoch_for_economics_data": 30, + } + type metricsResp struct { + Metrics map[string]interface{} `json:"metrics"` + } + resp := struct { + Data metricsResp `json:"data"` + Error string `json:"error"` + Code string `json:"code"` + }{ + Data: metricsResp{Metrics: responsStatus}, + Code: string(data.ReturnCodeSuccess), + } + + responseBuff, _ := json.Marshal(&resp) + _, err := rw.Write(responseBuff) + log.LogIfError(err) +} + func (ths *TestHttpServer) processRequestGetConfigMetrics(rw http.ResponseWriter, _ *http.Request) { responseStatus := map[string]interface{}{ "erd_chain_id": "testnet",