You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
4.9 KiB
189 lines
4.9 KiB
package services
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/coinbase/rosetta-sdk-go/types"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/harmony-one/harmony/core/rawdb"
|
|
hmyTypes "github.com/harmony-one/harmony/core/types"
|
|
"github.com/harmony-one/harmony/hmy"
|
|
internal_common "github.com/harmony-one/harmony/internal/common"
|
|
rosetta_common "github.com/harmony-one/harmony/rosetta/common"
|
|
)
|
|
|
|
// SearchAPI implements the server.SearchAPIServicer interface.
|
|
type SearchAPI struct {
|
|
hmy *hmy.Harmony
|
|
}
|
|
|
|
func NewSearchAPI(hmy *hmy.Harmony) *SearchAPI {
|
|
return &SearchAPI{hmy: hmy}
|
|
}
|
|
|
|
// SearchTransactions implements the /search/transactions endpoint
|
|
func (s *SearchAPI) SearchTransactions(ctx context.Context, request *types.SearchTransactionsRequest) (resp *types.SearchTransactionsResponse, err *types.Error) {
|
|
cacheItem, cacheHelper, cacheErr := rosettaCacheHelper("SearchTransactions", request)
|
|
if cacheErr == nil {
|
|
if cacheItem != nil {
|
|
return cacheItem.resp.(*types.SearchTransactionsResponse), nil
|
|
} else {
|
|
defer cacheHelper(resp, err)
|
|
}
|
|
}
|
|
|
|
if err := assertValidNetworkIdentifier(request.NetworkIdentifier, s.hmy.ShardID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var offset, limit int64
|
|
if request.Limit == nil {
|
|
limit = 10
|
|
} else {
|
|
limit = *request.Limit
|
|
if limit > 1000 {
|
|
limit = 1000
|
|
}
|
|
}
|
|
|
|
var filteredHash, rangeHash []common.Hash
|
|
|
|
if request.AccountIdentifier != nil {
|
|
ddr, err := internal_common.ParseAddr(request.AccountIdentifier.Address)
|
|
if err != nil {
|
|
return nil, &rosetta_common.ErrCallParametersInvalid
|
|
}
|
|
|
|
address, err := internal_common.AddressToBech32(ddr)
|
|
if err != nil {
|
|
return nil, &rosetta_common.ErrCallParametersInvalid
|
|
}
|
|
|
|
histories, err := s.hmy.GetTransactionsHistory(address, "", "")
|
|
if err != nil {
|
|
return nil, rosetta_common.NewError(rosetta_common.CatchAllError, map[string]interface{}{
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
filteredHash = histories
|
|
}
|
|
|
|
if request.TransactionIdentifier != nil {
|
|
hash := common.HexToHash(request.TransactionIdentifier.Hash)
|
|
filteredHash = operatorFilter(request.Operator, filteredHash, []common.Hash{hash})
|
|
}
|
|
|
|
resp = &types.SearchTransactionsResponse{}
|
|
if int64(len(filteredHash)) < offset {
|
|
return resp, nil
|
|
} else if int64(len(filteredHash)) < offset+limit {
|
|
rangeHash = filteredHash[offset:]
|
|
} else {
|
|
rangeHash = filteredHash[offset : offset+limit]
|
|
}
|
|
|
|
for _, hash := range rangeHash {
|
|
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.hmy.ChainDb(), hash)
|
|
if tx == nil {
|
|
return nil, rosetta_common.NewError(rosetta_common.CatchAllError, map[string]interface{}{
|
|
"message": "can not get tx info by hash",
|
|
})
|
|
}
|
|
|
|
info, err := buildFromTXInfo(tx, blockHash, blockNumber, index)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp.Transactions = append(resp.Transactions, info)
|
|
}
|
|
|
|
resp.TotalCount = int64(len(resp.Transactions))
|
|
if offset+limit < int64(len(filteredHash)) {
|
|
resp.NextOffset = &resp.TotalCount
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func buildFromTXInfo(tx *hmyTypes.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) (*types.BlockTransaction, *types.Error) {
|
|
receiverAccountID, rosettaError := newAccountIdentifier(*tx.To())
|
|
if rosettaError != nil {
|
|
return nil, rosettaError
|
|
}
|
|
|
|
var typ string
|
|
if tx.To() == nil {
|
|
typ = rosetta_common.ContractCreationOperation
|
|
} else if tx.ShardID() != tx.ToShardID() {
|
|
typ = rosetta_common.NativeCrossShardTransferOperation
|
|
} else {
|
|
typ = rosetta_common.NativeTransferOperation
|
|
}
|
|
|
|
return &types.BlockTransaction{
|
|
BlockIdentifier: &types.BlockIdentifier{
|
|
Index: int64(blockNumber),
|
|
Hash: blockHash.Hex(),
|
|
},
|
|
Transaction: &types.Transaction{
|
|
TransactionIdentifier: &types.TransactionIdentifier{
|
|
Hash: tx.Hash().String(),
|
|
},
|
|
Operations: []*types.Operation{
|
|
{
|
|
OperationIdentifier: &types.OperationIdentifier{
|
|
Index: int64(index),
|
|
},
|
|
Status: &rosetta_common.SuccessOperationStatus.Status,
|
|
Type: typ,
|
|
Account: receiverAccountID,
|
|
Amount: &types.Amount{
|
|
Value: tx.Value().String(),
|
|
Currency: &rosetta_common.NativeCurrency,
|
|
},
|
|
},
|
|
},
|
|
Metadata: map[string]interface{}{
|
|
"index": index,
|
|
"size": tx.Size().String(),
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func operatorFilter(operator *types.Operator, hashesArr ...[]common.Hash) (ret []common.Hash) {
|
|
if len(hashesArr) == 0 {
|
|
return ret
|
|
}
|
|
|
|
if operator == nil || *operator == types.AND { // operator is and
|
|
filterMap := make(map[string]common.Hash)
|
|
for _, hash := range hashesArr[0] {
|
|
filterMap[hash.Hex()] = hash
|
|
}
|
|
|
|
for _, hashes := range hashesArr[1:] {
|
|
filteredMap := make(map[string]common.Hash)
|
|
|
|
for _, hash := range hashes {
|
|
if _, ok := filterMap[hash.Hex()]; ok {
|
|
filteredMap[hash.Hex()] = hash
|
|
}
|
|
}
|
|
|
|
filterMap = filteredMap
|
|
}
|
|
|
|
for _, hash := range filterMap {
|
|
ret = append(ret, hash)
|
|
}
|
|
} else { // operator is or
|
|
for _, hashes := range hashesArr {
|
|
ret = append(ret, hashes...)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|