Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get transaction with status api route #1969

Merged
merged 11 commits into from
Jun 24, 2020
6 changes: 0 additions & 6 deletions api/mock/facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,10 @@ type Facade struct {
ComputeTransactionGasLimitHandler func(tx *transaction.Transaction) (uint64, error)
NodeConfigCalled func() map[string]interface{}
GetQueryHandlerCalled func(name string) (debug.QueryHandler, error)
GetTransactionStatusCalled func(hash string) (string, error)
GetValueForKeyCalled func(address string, key string) (string, error)
GetPeerInfoCalled func(pid string) ([]core.QueryP2PPeerInfo, error)
}

// GetTransactionStatus -
func (f *Facade) GetTransactionStatus(hash string) (string, error) {
return f.GetTransactionStatusCalled(hash)
}

// RestApiInterface -
func (f *Facade) RestApiInterface() string {
return "localhost:8080"
Expand Down
35 changes: 6 additions & 29 deletions api/transaction/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ type TxService interface {
ValidateTransaction(tx *transaction.Transaction) error
SendBulkTransactions([]*transaction.Transaction) (uint64, error)
GetTransaction(hash string) (*transaction.ApiTransactionResult, error)
GetTransactionStatus(hash string) (string, error)
ComputeTransactionGasLimit(tx *transaction.Transaction) (uint64, error)
EncodeAddressPubkey(pk []byte) (string, error)
IsInterfaceNil() bool
Expand Down Expand Up @@ -68,7 +67,6 @@ func Routes(router *wrapper.RouterWrapper) {
router.RegisterHandler(http.MethodPost, "/cost", ComputeTransactionGasLimit)
router.RegisterHandler(http.MethodPost, "/send-multiple", SendMultipleTransactions)
router.RegisterHandler(http.MethodGet, "/:txhash", GetTransaction)
router.RegisterHandler(http.MethodGet, "/:txhash/status", GetTransactionStatus)
iulianpascalau marked this conversation as resolved.
Show resolved Hide resolved
}

// SendTransaction will receive a transaction from the client and propagate it for processing
Expand Down Expand Up @@ -132,11 +130,13 @@ func SendMultipleTransactions(c *gin.Context) {
return
}

var txs []*transaction.Transaction
var tx *transaction.Transaction
var txHash []byte
var (
txs []*transaction.Transaction
tx *transaction.Transaction
txHash []byte
)

txsHashes := make(map[int]string, 0)
txsHashes := make(map[int]string)
iulianpascalau marked this conversation as resolved.
Show resolved Hide resolved
for idx, receivedTx := range gtx {
tx, txHash, err = ef.CreateTransaction(
receivedTx.Nonce,
Expand Down Expand Up @@ -199,29 +199,6 @@ func GetTransaction(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"transaction": tx})
}

// GetTransactionStatus returns the status of a transaction identified by the given hash
func GetTransactionStatus(c *gin.Context) {
ef, ok := c.MustGet("elrondFacade").(TxService)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": errors.ErrInvalidAppContext.Error()})
return
}

txhash := c.Param("txhash")
if txhash == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s: %s", errors.ErrValidation.Error(), errors.ErrValidationEmptyTxHash.Error())})
return
}

status, err := ef.GetTransactionStatus(txhash)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": errors.ErrGetTransaction.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"status": status})
}

// ComputeTransactionGasLimit returns how many gas units a transaction wil consume
func ComputeTransactionGasLimit(c *gin.Context) {
ef, ok := c.MustGet("elrondFacade").(TxService)
Expand Down
39 changes: 0 additions & 39 deletions api/transaction/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ type TransactionHashResponse struct {
TxHash string `json:"txHash,omitempty"`
}

type TransactionStatusResponse struct {
GeneralResponse
Status string `json:"status"`
}

type TransactionCostResponse struct {
GeneralResponse
Cost uint64 `json:"txGasUnits"`
Expand All @@ -53,40 +48,6 @@ func init() {
gin.SetMode(gin.TestMode)
}

func TestGetTransactionStatus(t *testing.T) {
rightHash := "hash"
wrongHash := "wronghash"
facade := mock.Facade{
GetTransactionStatusCalled: func(hash string) (string, error) {
if hash == rightHash {
return "pending", nil
}
return "unknown", nil
},
}

req, _ := http.NewRequest("GET", "/transaction/"+wrongHash+"/status", nil)
ws := startNodeServer(&facade)
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

response := TransactionStatusResponse{}
loadResponse(resp.Body, &response)

assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, "unknown", response.Status)

req, _ = http.NewRequest("GET", "/transaction/"+rightHash+"/status", nil)
resp = httptest.NewRecorder()
ws.ServeHTTP(resp, req)

response = TransactionStatusResponse{}
loadResponse(resp.Body, &response)

assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, "pending", response.Status)
}

func TestGetTransaction_WithCorrectHashShouldReturnTransaction(t *testing.T) {
sender := "sender"
receiver := "receiver"
Expand Down
8 changes: 6 additions & 2 deletions core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,17 @@ const MetaChainSystemSCsCost = "MetaChainSystemSCsCost"
// TransactionStatus is the type used to represent the status of a transaction
type TransactionStatus string

func (ts TransactionStatus) String() string {
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
return string(ts)
}

const (
// TxStatusReceived represents the status of a transaction which was received but not yet executed
TxStatusReceived TransactionStatus = "received"
// TxStatusPartiallyExecuted represent the status of a transaction which was received and executed on source shard
TxStatusPartiallyExecuted TransactionStatus = "partially-executed"
// TxStatusExecuted represents the status of a transaction which was received and executed
TxStatusExecuted TransactionStatus = "executed"
// TxStatusUnknown represents the status returned for a missing transaction
TxStatusUnknown TransactionStatus = "unknown"
)

const (
Expand Down
1 change: 1 addition & 0 deletions data/transaction/apiTransactionResult.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ type ApiTransactionResult struct {
Data string `json:"data,omitempty"`
Code string `json:"code,omitempty"`
Signature string `json:"signature,omitempty"`
Status string `json:"status,omitempty"`
}
3 changes: 0 additions & 3 deletions facade/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ type NodeHandler interface {
//GetTransaction will return a transaction based on the hash
GetTransaction(hash string) (*transaction.ApiTransactionResult, error)

//GetTransactionStatus gets the transaction status
GetTransactionStatus(hash string) (string, error)

// GetAccount returns an accountResponse containing information
// about the account corelated with provided address
GetAccount(address string) (state.UserAccountHandler, error)
Expand Down
10 changes: 0 additions & 10 deletions facade/mock/nodeStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ type NodeStub struct {
DirectTriggerCalled func(epoch uint32) error
IsSelfTriggerCalled func() bool
GetQueryHandlerCalled func(name string) (debug.QueryHandler, error)
GetTransactionStatusCalled func(hash string) (string, error)
GetValueForKeyCalled func(address string, key string) (string, error)
GetPeerInfoCalled func(pid string) ([]core.QueryP2PPeerInfo, error)
}
Expand All @@ -46,15 +45,6 @@ func (ns *NodeStub) GetValueForKey(address string, key string) (string, error) {
return "", nil
}

// GetTransactionStatus -
func (ns *NodeStub) GetTransactionStatus(hash string) (string, error) {
if ns.GetTransactionStatusCalled != nil {
return ns.GetTransactionStatusCalled(hash)
}

return "unknown", nil
}

// EncodeAddressPubkey -
func (ns *NodeStub) EncodeAddressPubkey(pk []byte) (string, error) {
return hex.EncodeToString(pk), nil
Expand Down
5 changes: 0 additions & 5 deletions facade/nodeFacade.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,6 @@ func (nf *nodeFacade) GetTransaction(hash string) (*transaction.ApiTransactionRe
return nf.node.GetTransaction(hash)
}

// GetTransactionStatus gets the current transaction status, given a specific tx hash
func (nf *nodeFacade) GetTransactionStatus(hash string) (string, error) {
return nf.node.GetTransactionStatus(hash)
}

// ComputeTransactionGasLimit will estimate how many gas a transaction will consume
func (nf *nodeFacade) ComputeTransactionGasLimit(tx *transaction.Transaction) (uint64, error) {
return nf.apiResolver.ComputeTransactionGasLimit(tx)
Expand Down
5 changes: 5 additions & 0 deletions node/export_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package node

import (
"github.com/ElrondNetwork/elrond-go/data"
"github.com/ElrondNetwork/elrond-go/p2p"
)

func (n *Node) CreateConsensusTopic(messageProcessor p2p.MessageProcessor) error {
return n.createConsensusTopic(messageProcessor)
}

func (n *Node) ComputeTransactionStatus(tx data.TransactionHandler, isInPool bool) string {
return n.computeTransactionStatus(tx, isInPool)
}
87 changes: 51 additions & 36 deletions node/nodeTransactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/ElrondNetwork/elrond-go/core"
"github.com/ElrondNetwork/elrond-go/data"
rewardTxData "github.com/ElrondNetwork/elrond-go/data/rewardTx"
"github.com/ElrondNetwork/elrond-go/data/smartContractResult"
"github.com/ElrondNetwork/elrond-go/data/transaction"
Expand Down Expand Up @@ -48,33 +49,6 @@ func (n *Node) GetTransaction(txHash string) (*transaction.ApiTransactionResult,
return nil, fmt.Errorf("transaction not found")
}

// GetTransactionStatus gets the transaction status
func (n *Node) GetTransactionStatus(txHash string) (string, error) {
if !n.apiTransactionByHashThrottler.CanProcess() {
return "", ErrSystemBusyTxHash
}

n.apiTransactionByHashThrottler.StartProcessing()
defer n.apiTransactionByHashThrottler.EndProcessing()

hash, err := hex.DecodeString(txHash)
if err != nil {
return "", err
}

_, _, foundInDataPool := n.getTxObjFromDataPool(hash)
if foundInDataPool {
return string(core.TxStatusReceived), nil
}

foundInStorage := n.isTxInStorage(hash)
if foundInStorage {
return string(core.TxStatusExecuted), nil
}

return string(core.TxStatusUnknown), nil
}

func (n *Node) getTxObjFromDataPool(hash []byte) (interface{}, transactionType, bool) {
txsPool := n.dataPool.Transactions()
txObj, found := txsPool.SearchFirstData(hash)
Expand Down Expand Up @@ -141,15 +115,18 @@ func (n *Node) castObjToTransaction(txObj interface{}, txType transactionType) (
switch txType {
case normalTx:
if tx, ok := txObj.(*transaction.Transaction); ok {
return n.prepareNormalTx(tx)
status := n.computeTransactionStatus(tx, true)
return n.prepareNormalTx(tx, status)
}
case rewardTx:
if tx, ok := txObj.(*rewardTxData.RewardTx); ok {
return n.prepareRewardTx(tx)
status := n.computeTransactionStatus(tx, true)
return n.prepareRewardTx(tx, status)
}
case unsignedTx:
if tx, ok := txObj.(*smartContractResult.SmartContractResult); ok {
return n.prepareUnsignedTx(tx)
status := n.computeTransactionStatus(tx, true)
return n.prepareUnsignedTx(tx, status)
}
}

Expand All @@ -164,28 +141,31 @@ func (n *Node) unmarshalTransaction(txBytes []byte, txType transactionType) (*tr
if err != nil {
return nil, err
}
return n.prepareNormalTx(&tx)
status := n.computeTransactionStatus(&tx, false)
return n.prepareNormalTx(&tx, status)
case rewardTx:
var tx rewardTxData.RewardTx
err := n.internalMarshalizer.Unmarshal(&tx, txBytes)
if err != nil {
return nil, err
}
return n.prepareRewardTx(&tx)
status := n.computeTransactionStatus(&tx, false)
return n.prepareRewardTx(&tx, status)

case unsignedTx:
var tx smartContractResult.SmartContractResult
err := n.internalMarshalizer.Unmarshal(&tx, txBytes)
if err != nil {
return nil, err
}
return n.prepareUnsignedTx(&tx)
status := n.computeTransactionStatus(&tx, false)
return n.prepareUnsignedTx(&tx, status)
default:
return &transaction.ApiTransactionResult{Type: string(invalidTx)}, nil // this shouldn't happen
}
}

func (n *Node) prepareNormalTx(tx *transaction.Transaction) (*transaction.ApiTransactionResult, error) {
func (n *Node) prepareNormalTx(tx *transaction.Transaction, status string) (*transaction.ApiTransactionResult, error) {
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
return &transaction.ApiTransactionResult{
Type: string(normalTx),
Nonce: tx.Nonce,
Expand All @@ -196,20 +176,24 @@ func (n *Node) prepareNormalTx(tx *transaction.Transaction) (*transaction.ApiTra
GasLimit: tx.GasLimit,
Data: string(tx.Data),
Signature: hex.EncodeToString(tx.Signature),
Status: status,
}, nil
}

func (n *Node) prepareRewardTx(tx *rewardTxData.RewardTx) (*transaction.ApiTransactionResult, error) {
func (n *Node) prepareRewardTx(tx *rewardTxData.RewardTx, status string) (*transaction.ApiTransactionResult, error) {
return &transaction.ApiTransactionResult{
Type: string(rewardTx),
Round: tx.GetRound(),
Epoch: tx.GetEpoch(),
Value: tx.GetValue().String(),
Receiver: n.addressPubkeyConverter.Encode(tx.GetRcvAddr()),
Status: status,
}, nil
}

func (n *Node) prepareUnsignedTx(tx *smartContractResult.SmartContractResult) (*transaction.ApiTransactionResult, error) {
func (n *Node) prepareUnsignedTx(
tx *smartContractResult.SmartContractResult, status string,
) (*transaction.ApiTransactionResult, error) {
return &transaction.ApiTransactionResult{
Type: string(unsignedTx),
Nonce: tx.GetNonce(),
Expand All @@ -221,5 +205,36 @@ func (n *Node) prepareUnsignedTx(tx *smartContractResult.SmartContractResult) (*
Data: string(tx.GetData()),
Code: string(tx.GetCode()),
Signature: "",
Status: status,
}, nil
}

func (n *Node) computeTransactionStatus(tx data.TransactionHandler, isInPool bool) string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can return TransactionStatus here, and perhaps also use this type for the new tx field - and let the marshelizer do its job (optional - and hopefully it will work).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json marshaler will be happy with that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes maybe json marshalizer will be happy with that but if we change core.TransactionStatus in a more complex structure that contains more than a single 'string' field will not work anymore. if you strongly suggest that ,i will change... wait for your answer.

andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
selfShardID := n.shardCoordinator.SelfId()
receiverShardID := n.shardCoordinator.ComputeId(tx.GetRcvAddr())

var senderShardID uint32
if sndAddr := tx.GetSndAddr(); sndAddr != nil {
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
senderShardID = n.shardCoordinator.ComputeId(tx.GetSndAddr())
} else {
// reward transaction (sender address is nil)
senderShardID = core.MetachainShardId
}

// transaction is in pool
if isInPool {
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
if selfShardID == receiverShardID && senderShardID != receiverShardID {
andreibancioiu marked this conversation as resolved.
Show resolved Hide resolved
// is in pool on destination shard
return core.TxStatusPartiallyExecuted.String()
}
// is in pool on source shard
return core.TxStatusReceived.String()
}
// transaction is in storage
if selfShardID == receiverShardID {
// is in storage on destination shard or is intra-shard
return core.TxStatusExecuted.String()
}
// is in storage on source shard
return core.TxStatusPartiallyExecuted.String()
}