diff --git a/api/groups/baseVmValuesGroup.go b/api/groups/baseVmValuesGroup.go index 31ec559f..011e9fbf 100644 --- a/api/groups/baseVmValuesGroup.go +++ b/api/groups/baseVmValuesGroup.go @@ -6,9 +6,11 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/vm" apiErrors "github.com/multiversx/mx-chain-proxy-go/api/errors" "github.com/multiversx/mx-chain-proxy-go/api/shared" + "github.com/multiversx/mx-chain-proxy-go/common" "github.com/multiversx/mx-chain-proxy-go/data" ) @@ -67,7 +69,7 @@ func (group *vmValuesGroup) getInt(context *gin.Context) { } func (group *vmValuesGroup) doGetVMValue(context *gin.Context, asType vm.ReturnDataKind) { - vmOutput, err := group.doExecuteQuery(context) + vmOutput, blockInfo, err := group.doExecuteQuery(context) if err != nil { returnBadRequest(context, "doGetVMValue", err) @@ -80,38 +82,43 @@ func (group *vmValuesGroup) doGetVMValue(context *gin.Context, asType vm.ReturnD return } - returnOkResponse(context, returnData) + returnOkResponse(context, returnData, blockInfo) } // executeQuery returns the data as string func (group *vmValuesGroup) executeQuery(context *gin.Context) { - vmOutput, err := group.doExecuteQuery(context) + vmOutput, blockInfo, err := group.doExecuteQuery(context) if err != nil { returnBadRequest(context, "executeQuery", err) return } - returnOkResponse(context, vmOutput) + returnOkResponse(context, vmOutput, blockInfo) } -func (group *vmValuesGroup) doExecuteQuery(context *gin.Context) (*vm.VMOutputApi, error) { +func (group *vmValuesGroup) doExecuteQuery(context *gin.Context) (*vm.VMOutputApi, data.BlockInfo, error) { request := VMValueRequest{} err := context.ShouldBindJSON(&request) if err != nil { - return nil, apiErrors.ErrInvalidJSONRequest + return nil, data.BlockInfo{}, apiErrors.ErrInvalidJSONRequest } command, err := createSCQuery(&request) if err != nil { - return nil, err + return nil, data.BlockInfo{}, err } - vmOutput, err := group.facade.ExecuteSCQuery(command) + command.BlockNonce, command.BlockHash, err = extractBlockCoordinates(context) if err != nil { - return nil, err + return nil, data.BlockInfo{}, err } - return vmOutput, nil + vmOutput, blockInfo, err := group.facade.ExecuteSCQuery(command) + if err != nil { + return nil, data.BlockInfo{}, err + } + + return vmOutput, blockInfo, nil } func createSCQuery(request *VMValueRequest) (*data.SCQuery, error) { @@ -136,11 +143,25 @@ func createSCQuery(request *VMValueRequest) (*data.SCQuery, error) { }, nil } +func extractBlockCoordinates(context *gin.Context) (core.OptionalUint64, []byte, error) { + blockNonce, err := parseUint64UrlParam(context, common.UrlParameterBlockNonce) + if err != nil { + return core.OptionalUint64{}, nil, fmt.Errorf("%w for block nonce", err) + } + + blockHash, err := parseHexBytesUrlParam(context, common.UrlParameterBlockHash) + if err != nil { + return core.OptionalUint64{}, nil, fmt.Errorf("%w for block hash", err) + } + + return blockNonce, blockHash, nil +} + func returnBadRequest(context *gin.Context, errScope string, err error) { message := fmt.Sprintf("%s: %s", errScope, err) shared.RespondWith(context, http.StatusBadRequest, nil, message, data.ReturnCodeRequestError) } -func returnOkResponse(context *gin.Context, dataToReturn interface{}) { - shared.RespondWith(context, http.StatusOK, gin.H{"data": dataToReturn}, "", data.ReturnCodeSuccess) +func returnOkResponse(context *gin.Context, dataToReturn interface{}, blockInfo interface{}) { + shared.RespondWith(context, http.StatusOK, gin.H{"data": dataToReturn, "blockInfo": blockInfo}, "", data.ReturnCodeSuccess) } diff --git a/api/groups/baseVmValuesGroup_test.go b/api/groups/baseVmValuesGroup_test.go index cadfd11e..51899b67 100644 --- a/api/groups/baseVmValuesGroup_test.go +++ b/api/groups/baseVmValuesGroup_test.go @@ -9,6 +9,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "strconv" "testing" "github.com/multiversx/mx-chain-core-go/data/vm" @@ -29,7 +30,8 @@ type simpleResponse struct { } type vmOutputResponse struct { - Data *vm.VMOutputApi `json:"data"` + Data *vm.VMOutputApi `json:"data"` + BlockInfo data.BlockInfo `json:"blockInfo"` } type vmOutputGenericResponse struct { @@ -53,10 +55,10 @@ func TestGetHex_ShouldWork(t *testing.T) { valueBuff, _ := hex.DecodeString("DEADBEEF") facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { return &vm.VMOutputApi{ ReturnData: [][]byte{valueBuff}, - }, nil + }, data.BlockInfo{}, nil }, } @@ -80,10 +82,10 @@ func TestGetString_ShouldWork(t *testing.T) { valueBuff := "DEADBEEF" facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { return &vm.VMOutputApi{ ReturnData: [][]byte{[]byte(valueBuff)}, - }, nil + }, data.BlockInfo{}, nil }, } @@ -107,12 +109,12 @@ func TestGetInt_ShouldWork(t *testing.T) { value := "1234567" facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { returnData := big.NewInt(0) returnData.SetString(value, 10) return &vm.VMOutputApi{ ReturnData: [][]byte{returnData.Bytes()}, - }, nil + }, data.BlockInfo{}, nil }, } @@ -134,11 +136,11 @@ func TestQuery_ShouldWork(t *testing.T) { t.Parallel() facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { return &vm.VMOutputApi{ ReturnData: [][]byte{big.NewInt(42).Bytes()}, - }, nil + }, data.BlockInfo{}, nil }, } @@ -156,6 +158,39 @@ func TestQuery_ShouldWork(t *testing.T) { require.Equal(t, int64(42), big.NewInt(0).SetBytes(response.Data.Data.ReturnData[0]).Int64()) } +func TestQuery_ShouldWorkWithCoordinates(t *testing.T) { + t.Parallel() + + providedNonce := uint64(123) + providedBlockInfo := data.BlockInfo{ + Nonce: providedNonce, + Hash: "block hash", + RootHash: "block rootHash", + } + facade := &mock.FacadeStub{ + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { + require.Equal(t, providedNonce, query.BlockNonce.Value) + return &vm.VMOutputApi{ + ReturnData: [][]byte{big.NewInt(42).Bytes()}, + }, providedBlockInfo, nil + }, + } + + request := groups.VMValueRequest{ + ScAddress: DummyScAddress, + FuncName: "function", + Args: []string{}, + } + + response := vmOutputGenericResponse{} + statusCode := doPost(t, facade, "/vm-values/query?blockNonce="+strconv.FormatUint(providedNonce, 10), request, &response) + + require.Equal(t, http.StatusOK, statusCode) + require.Equal(t, "", response.Error) + require.Equal(t, int64(42), big.NewInt(0).SetBytes(response.Data.Data.ReturnData[0]).Int64()) + require.Equal(t, providedBlockInfo, response.Data.BlockInfo) +} + func TestCreateSCQuery_ArgumentIsNotHexShouldErr(t *testing.T) { request := groups.VMValueRequest{ ScAddress: DummyScAddress, @@ -173,8 +208,8 @@ func TestAllRoutes_FacadeErrorsShouldErr(t *testing.T) { errExpected := errors.New("some random error") facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { - return nil, errExpected + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { + return nil, data.BlockInfo{}, errExpected }, } @@ -192,8 +227,8 @@ func TestAllRoutes_WhenBadArgumentsShouldErr(t *testing.T) { errExpected := errors.New("not a valid hex string") facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { - return &vm.VMOutputApi{}, nil + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { + return &vm.VMOutputApi{}, data.BlockInfo{}, nil }, } @@ -211,8 +246,8 @@ func TestAllRoutes_WhenNoVMReturnDataShouldErr(t *testing.T) { errExpected := errors.New("no return data") facade := mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { - return &vm.VMOutputApi{}, nil + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { + return &vm.VMOutputApi{}, data.BlockInfo{}, nil }, } @@ -229,8 +264,8 @@ func TestAllRoutes_WhenBadJsonShouldErr(t *testing.T) { t.Parallel() facade := mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { - return &vm.VMOutputApi{}, nil + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { + return &vm.VMOutputApi{}, data.BlockInfo{}, nil }, } @@ -241,10 +276,10 @@ func TestAllRoutes_WithSameScStateAndShouldBySyncedFilled(t *testing.T) { t.Parallel() facade := &mock.FacadeStub{ - ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, e error) { + ExecuteSCQueryHandler: func(query *data.SCQuery) (vmOutput *vm.VMOutputApi, blockInfo data.BlockInfo, e error) { require.True(t, query.ShouldBeSynced) require.True(t, query.SameScState) - return &vm.VMOutputApi{}, nil + return &vm.VMOutputApi{}, data.BlockInfo{}, nil }, } diff --git a/api/groups/interface.go b/api/groups/interface.go index 7b95c63b..253a3327 100644 --- a/api/groups/interface.go +++ b/api/groups/interface.go @@ -123,7 +123,7 @@ type ValidatorFacadeHandler interface { // VmValuesFacadeHandler interface defines methods that can be used from the facade type VmValuesFacadeHandler interface { - ExecuteSCQuery(*data.SCQuery) (*vm.VMOutputApi, error) + ExecuteSCQuery(*data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) } // ActionsFacadeHandler interface defines methods that can be used from the facade diff --git a/api/mock/facadeStub.go b/api/mock/facadeStub.go index 11b7e698..abf744a8 100644 --- a/api/mock/facadeStub.go +++ b/api/mock/facadeStub.go @@ -33,7 +33,7 @@ type FacadeStub struct { SendMultipleTransactionsHandler func(txs []*data.Transaction) (data.MultipleTransactionsResponseData, error) SimulateTransactionHandler func(tx *data.Transaction, checkSignature bool) (*data.GenericAPIResponse, error) SendUserFundsCalled func(receiver string, value *big.Int) error - ExecuteSCQueryHandler func(query *data.SCQuery) (*vm.VMOutputApi, error) + ExecuteSCQueryHandler func(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) GetHeartbeatDataHandler func() (*data.HeartbeatResponse, error) ValidatorStatisticsHandler func() (map[string]*data.ValidatorApiResponse, error) TransactionCostRequestHandler func(tx *data.Transaction) (*data.TxCostResponseData, error) @@ -413,7 +413,7 @@ func (f *FacadeStub) SendUserFunds(receiver string, value *big.Int) error { } // ExecuteSCQuery - -func (f *FacadeStub) ExecuteSCQuery(query *data.SCQuery) (*vm.VMOutputApi, error) { +func (f *FacadeStub) ExecuteSCQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return f.ExecuteSCQueryHandler(query) } diff --git a/data/vmValues.go b/data/vmValues.go index 5a62d6c6..9020bee9 100644 --- a/data/vmValues.go +++ b/data/vmValues.go @@ -1,12 +1,14 @@ package data import ( + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/vm" ) // VmValuesResponseData follows the format of the data field in an API response for a VM values query type VmValuesResponseData struct { - Data *vm.VMOutputApi `json:"data"` + Data *vm.VMOutputApi `json:"data"` + BlockInfo BlockInfo `json:"blockInfo"` } // ResponseVmValue defines a wrapper over string containing returned data in hex format @@ -36,4 +38,6 @@ type SCQuery struct { SameScState bool `json:"sameScState"` ShouldBeSynced bool `json:"shouldBeSynced"` Arguments [][]byte + BlockNonce core.OptionalUint64 + BlockHash []byte } diff --git a/facade/baseFacade.go b/facade/baseFacade.go index 5f67cb08..3a72611a 100644 --- a/facade/baseFacade.go +++ b/facade/baseFacade.go @@ -297,7 +297,7 @@ func (epf *ProxyFacade) getNetworkConfig() (*data.NetworkConfig, error) { } // ExecuteSCQuery retrieves data from existing SC trie through the use of a VM -func (epf *ProxyFacade) ExecuteSCQuery(query *data.SCQuery) (*vm.VMOutputApi, error) { +func (epf *ProxyFacade) ExecuteSCQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return epf.scQueryService.ExecuteQuery(query) } diff --git a/facade/baseFacade_test.go b/facade/baseFacade_test.go index 54b87136..4e136fa2 100644 --- a/facade/baseFacade_test.go +++ b/facade/baseFacade_test.go @@ -570,9 +570,9 @@ func TestProxyFacade_GetDataValue(t *testing.T) { &mock.AccountProcessorStub{}, &mock.TransactionProcessorStub{}, &mock.SCQueryServiceStub{ - ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, error) { + ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { wasCalled = true - return &vm.VMOutputApi{}, nil + return &vm.VMOutputApi{}, data.BlockInfo{}, nil }, }, &mock.NodeGroupProcessorStub{}, @@ -588,7 +588,7 @@ func TestProxyFacade_GetDataValue(t *testing.T) { &mock.AboutInfoProcessorStub{}, ) - _, _ = epf.ExecuteSCQuery(nil) + _, _, _ = epf.ExecuteSCQuery(nil) assert.True(t, wasCalled) } diff --git a/facade/interface.go b/facade/interface.go index b03761ae..ceb04241 100644 --- a/facade/interface.go +++ b/facade/interface.go @@ -62,7 +62,7 @@ type ProofProcessor interface { // SCQueryService defines how data should be get from a SC account type SCQueryService interface { - ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, error) + ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) } // NodeGroupProcessor defines what a node group processor should do diff --git a/facade/mock/scQueryServiceStub.go b/facade/mock/scQueryServiceStub.go index b6b98462..e57ee1c1 100644 --- a/facade/mock/scQueryServiceStub.go +++ b/facade/mock/scQueryServiceStub.go @@ -7,10 +7,10 @@ import ( // SCQueryServiceStub - type SCQueryServiceStub struct { - ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, error) + ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) } // ExecuteQuery - -func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, error) { +func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return serviceStub.ExecuteQueryCalled(query) } diff --git a/process/esdtSupplyProcessor.go b/process/esdtSupplyProcessor.go index 4c02d029..957bfb6d 100644 --- a/process/esdtSupplyProcessor.go +++ b/process/esdtSupplyProcessor.go @@ -145,7 +145,7 @@ func (esp *esdtSupplyProcessor) getInitialSupplyFromMeta(token string) (*big.Int Arguments: [][]byte{[]byte(token)}, } - res, err := esp.scQueryProc.ExecuteQuery(scQuery) + res, _, err := esp.scQueryProc.ExecuteQuery(scQuery) if err != nil { return nil, err } diff --git a/process/esdtSupplyProcessor_test.go b/process/esdtSupplyProcessor_test.go index 2e195fbc..d73dda9b 100644 --- a/process/esdtSupplyProcessor_test.go +++ b/process/esdtSupplyProcessor_test.go @@ -56,10 +56,10 @@ func TestEsdtSupplyProcessor_GetESDTSupplyFungible(t *testing.T) { }, } scQueryProc := &mock.SCQueryServiceStub{ - ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, error) { + ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return &vm.VMOutputApi{ ReturnData: [][]byte{nil, nil, nil, []byte("500")}, - }, nil + }, data.BlockInfo{}, nil }, } esdtProc, err := NewESDTSupplyProcessor(baseProc, scQueryProc) @@ -163,10 +163,10 @@ func TestEsdtSupplyProcessor_GetESDTSupplyShouldReturnErrorIfInconsistentRespons }, } scQueryProc := &mock.SCQueryServiceStub{ - ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, error) { + ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return &vm.VMOutputApi{ ReturnData: [][]byte{nil, nil, nil, []byte("500")}, - }, nil + }, data.BlockInfo{}, nil }, } esdtProc, err := NewESDTSupplyProcessor(baseProc, scQueryProc) @@ -220,10 +220,10 @@ func TestEsdtSupplyProcessor_GetESDTSupplyShouldReturnRecomputed(t *testing.T) { }, } scQueryProc := &mock.SCQueryServiceStub{ - ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, error) { + ExecuteQueryCalled: func(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return &vm.VMOutputApi{ ReturnData: [][]byte{nil, nil, nil, []byte("500")}, - }, nil + }, data.BlockInfo{}, nil }, } esdtProc, err := NewESDTSupplyProcessor(baseProc, scQueryProc) diff --git a/process/interface.go b/process/interface.go index 3230a4b1..3619701d 100644 --- a/process/interface.go +++ b/process/interface.go @@ -75,7 +75,7 @@ type LogsMergerHandler interface { // SCQueryService defines how data should be get from a SC account type SCQueryService interface { - ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, error) + ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) IsInterfaceNil() bool } diff --git a/process/mock/scQueryServiceStub.go b/process/mock/scQueryServiceStub.go index 0c853bb0..9d00f4d7 100644 --- a/process/mock/scQueryServiceStub.go +++ b/process/mock/scQueryServiceStub.go @@ -7,11 +7,11 @@ import ( // SCQueryServiceStub is a stub type SCQueryServiceStub struct { - ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, error) + ExecuteQueryCalled func(*data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) } // ExecuteQuery is a stub -func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, error) { +func (serviceStub *SCQueryServiceStub) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { return serviceStub.ExecuteQueryCalled(query) } diff --git a/process/scQueryProcessor.go b/process/scQueryProcessor.go index af5206f4..355dff70 100644 --- a/process/scQueryProcessor.go +++ b/process/scQueryProcessor.go @@ -11,8 +11,10 @@ import ( "github.com/multiversx/mx-chain-proxy-go/data" ) -// SCQueryServicePath defines the get values path at which the nodes answer -const SCQueryServicePath = "/vm-values/query" +// scQueryServicePath defines the get values path at which the nodes answer +const scQueryServicePath = "/vm-values/query" +const blockNonceSuffix = "?blockNonce=" +const blockHashSuffix = "?blockHash=" // SCQueryProcessor is able to process smart contract queries type SCQueryProcessor struct { @@ -36,27 +38,35 @@ func NewSCQueryProcessor(proc Processor, pubKeyConverter core.PubkeyConverter) ( } // ExecuteQuery resolves the request by sending the request to the right observer and replies back the answer -func (scQueryProcessor *SCQueryProcessor) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, error) { +func (scQueryProcessor *SCQueryProcessor) ExecuteQuery(query *data.SCQuery) (*vm.VMOutputApi, data.BlockInfo, error) { addressBytes, err := scQueryProcessor.pubKeyConverter.Decode(query.ScAddress) if err != nil { - return nil, err + return nil, data.BlockInfo{}, err } shardID, err := scQueryProcessor.proc.ComputeShardId(addressBytes) if err != nil { - return nil, err + return nil, data.BlockInfo{}, err } observers, err := scQueryProcessor.proc.GetObservers(shardID) if err != nil { - return nil, err + return nil, data.BlockInfo{}, err } for _, observer := range observers { request := scQueryProcessor.createRequestFromQuery(query) response := &data.ResponseVmValue{} - httpStatus, err := scQueryProcessor.proc.CallPostRestEndPoint(observer.Address, SCQueryServicePath, request, response) + path := scQueryServicePath + if len(query.BlockHash) > 0 { + path = fmt.Sprintf("%s%s%s", path, blockHashSuffix, hex.EncodeToString(query.BlockHash)) + } + if query.BlockNonce.HasValue { + path = fmt.Sprintf("%s%s%d", path, blockNonceSuffix, query.BlockNonce.Value) + } + + httpStatus, err := scQueryProcessor.proc.CallPostRestEndPoint(observer.Address, path, request, response) isObserverDown := httpStatus == http.StatusNotFound || httpStatus == http.StatusRequestTimeout isOk := httpStatus == http.StatusOK responseHasExplicitError := len(response.Error) > 0 @@ -68,17 +78,17 @@ func (scQueryProcessor *SCQueryProcessor) ExecuteQuery(query *data.SCQuery) (*vm if isOk { log.Debug("SC query sent successfully, received response", "observer", observer.Address, "shard", shardID) - return response.Data.Data, nil + return response.Data.Data, response.Data.BlockInfo, nil } if responseHasExplicitError { - return nil, fmt.Errorf(response.Error) + return nil, data.BlockInfo{}, fmt.Errorf(response.Error) } - return nil, err + return nil, data.BlockInfo{}, err } - return nil, ErrSendingRequest + return nil, data.BlockInfo{}, ErrSendingRequest } func (scQueryProcessor *SCQueryProcessor) createRequestFromQuery(query *data.SCQuery) data.VmValueRequest { diff --git a/process/scQueryProcessor_test.go b/process/scQueryProcessor_test.go index a4e35932..7a893709 100644 --- a/process/scQueryProcessor_test.go +++ b/process/scQueryProcessor_test.go @@ -3,8 +3,11 @@ package process import ( "errors" "net/http" + "strconv" + "strings" "testing" + "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/pubkeyConverter" "github.com/multiversx/mx-chain-core-go/data/vm" "github.com/multiversx/mx-chain-proxy-go/data" @@ -49,7 +52,7 @@ func TestSCQueryProcessor_ExecuteQueryComputeShardIdFailsShouldErr(t *testing.T) }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) + value, _, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) require.Empty(t, value) require.Equal(t, errExpected, err) } @@ -67,7 +70,7 @@ func TestSCQueryProcessor_ExecuteQueryGetObserversFailsShouldErr(t *testing.T) { }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) + value, _, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) require.Empty(t, value) require.Equal(t, errExpected, err) } @@ -91,7 +94,7 @@ func TestSCQueryProcessor_ExecuteQuerySendingFailsOnAllObserversShouldErr(t *tes }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) + value, _, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) require.Empty(t, value) require.Equal(t, ErrSendingRequest, err) } @@ -99,6 +102,11 @@ func TestSCQueryProcessor_ExecuteQuerySendingFailsOnAllObserversShouldErr(t *tes func TestSCQueryProcessor_ExecuteQuery(t *testing.T) { t.Parallel() + providedBlockInfo := data.BlockInfo{ + Nonce: 123, + Hash: "block hash", + RootHash: "block rootHash", + } processor, _ := NewSCQueryProcessor(&mock.ProcessorStub{ ComputeShardIdCalled: func(addressBuff []byte) (u uint32, e error) { return 0, nil @@ -112,12 +120,13 @@ func TestSCQueryProcessor_ExecuteQuery(t *testing.T) { response.(*data.ResponseVmValue).Data.Data = &vm.VMOutputApi{ ReturnData: [][]byte{{42}}, } + response.(*data.ResponseVmValue).Data.BlockInfo = providedBlockInfo return http.StatusOK, nil }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ + value, blockInfo, err := processor.ExecuteQuery(&data.SCQuery{ ScAddress: dummyScAddress, FuncName: "function", Arguments: [][]byte{[]byte("aa")}, @@ -125,6 +134,53 @@ func TestSCQueryProcessor_ExecuteQuery(t *testing.T) { require.Nil(t, err) require.Equal(t, byte(42), value.ReturnData[0][0]) + require.Equal(t, providedBlockInfo, blockInfo) +} + +func TestSCQueryProcessor_ExecuteQueryWithCoordinates(t *testing.T) { + t.Parallel() + + providedNonce := uint64(123) + providedBlockInfo := data.BlockInfo{ + Nonce: providedNonce, + Hash: "block hash", + RootHash: "block rootHash", + } + processor, _ := NewSCQueryProcessor(&mock.ProcessorStub{ + ComputeShardIdCalled: func(addressBuff []byte) (u uint32, e error) { + return 0, nil + }, + GetObserversCalled: func(shardId uint32) (observers []*data.NodeData, e error) { + return []*data.NodeData{ + {Address: "adress1", ShardId: 0}, + }, nil + }, + CallPostRestEndPointCalled: func(address string, path string, dataValue interface{}, response interface{}) (int, error) { + require.True(t, strings.Contains(path, "blockNonce")) + require.True(t, strings.Contains(path, strconv.FormatUint(providedNonce, 10))) + + response.(*data.ResponseVmValue).Data.Data = &vm.VMOutputApi{ + ReturnData: [][]byte{{42}}, + } + response.(*data.ResponseVmValue).Data.BlockInfo = providedBlockInfo + + return http.StatusOK, nil + }, + }, testPubKeyConverter) + + value, blockInfo, err := processor.ExecuteQuery(&data.SCQuery{ + ScAddress: dummyScAddress, + FuncName: "function", + Arguments: [][]byte{[]byte("aa")}, + BlockNonce: core.OptionalUint64{ + Value: providedNonce, + HasValue: true, + }, + }) + + require.Nil(t, err) + require.Equal(t, byte(42), value.ReturnData[0][0]) + require.Equal(t, providedBlockInfo, blockInfo) } func TestSCQueryProcessor_ExecuteQueryFailsOnRandomErrorShouldErr(t *testing.T) { @@ -146,7 +202,7 @@ func TestSCQueryProcessor_ExecuteQueryFailsOnRandomErrorShouldErr(t *testing.T) }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) + value, _, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) require.Empty(t, value) require.Equal(t, errExpected, err) } @@ -171,7 +227,7 @@ func TestSCQueryProcessor_ExecuteQueryFailsOnBadRequestWithExplicitErrorShouldEr }, }, testPubKeyConverter) - value, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) + value, _, err := processor.ExecuteQuery(&data.SCQuery{ScAddress: dummyScAddress}) require.Empty(t, value) require.Equal(t, errExpected, err) }