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

Allow ioctl to show the list of actions for an account #2750

Merged
merged 12 commits into from
Sep 17, 2021
182 changes: 53 additions & 129 deletions ioctl/cmd/account/accountactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,20 @@
package account

import (
"context"
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/golang/protobuf/ptypes"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"github.com/rodaine/table"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"

"github.com/iotexproject/iotex-core/ioctl/config"
"github.com/iotexproject/iotex-core/ioctl/output"
"github.com/iotexproject/iotex-core/ioctl/util"
"github.com/iotexproject/iotex-proto/golang/iotexapi"
"github.com/iotexproject/iotex-proto/golang/iotextypes"
)

var countLimit *uint64

// Multi-language support
var (
actionsCmdShorts = map[config.Language]string{
Expand All @@ -37,12 +31,25 @@ var (
config.English: "actions (ALIAS|ADDRESS) [SKIP]",
config.Chinese: "actions (ALIAS|ADDRESS) [SKIP]",
}
flagCountUsages = map[config.Language]string{
config.English: "choose a count limit",
config.Chinese: "选择一个计数限制",
}
)

// TODO: remove struct definition after the release of anlytics v2
type allActionsByAddressResult struct {
ActHash string
BlkHeight string
Sender string
Recipient string
ActType string
Amount string
TimeStamp string
RecordType string
}

type allActionsByAddressResponse struct {
Count string
Results []*allActionsByAddressResult
}

// accountActionsCmd represents the account sign command
var accountActionsCmd = &cobra.Command{
Use: config.TranslateInLang(actionsCmdUses, config.UILanguage),
Expand All @@ -55,12 +62,8 @@ var accountActionsCmd = &cobra.Command{
},
}

func init() {
countLimit = accountActionsCmd.Flags().Uint64("limit", 15, config.TranslateInLang(flagCountUsages, config.UILanguage))
}

func accountActions(args []string) error {
var skip uint64
var skip uint64 = 0
var err error
if len(args) == 2 {
skip, err = strconv.ParseUint(args[1], 10, 64)
Expand All @@ -73,130 +76,51 @@ func accountActions(args []string) error {
if err != nil {
return output.NewError(output.AddressError, "failed to get address", err)
}

conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
if err != nil {
return output.NewError(output.NetworkError, "failed to connect to endpoint", err)
reqData := map[string]string{
"address": addr,
"offset": fmt.Sprint(skip),
}
defer conn.Close()
cli := iotexapi.NewAPIServiceClient(conn)
ctx := context.Background()

jwtMD, err := util.JwtAuth()
if err == nil {
ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
}

requestGetAccount := iotexapi.GetAccountRequest{
Address: addr,
}
accountResponse, err := cli.GetAccount(ctx, &requestGetAccount)
jsonData, err := json.Marshal(reqData)
if err != nil {
return output.NewError(output.APIError, "failed to get account", err)
}
numActions := accountResponse.AccountMeta.GetNumActions()
fmt.Println("Total:", numActions)
requestGetAction := iotexapi.GetActionsRequest{
Lookup: &iotexapi.GetActionsRequest_ByAddr{
ByAddr: &iotexapi.GetActionsByAddressRequest{
Address: addr,
Start: numActions - *countLimit - skip,
Count: *countLimit,
},
},
return output.NewError(output.ConvertError, "failed to pack in json", nil)
}
response, err := cli.GetActions(ctx, &requestGetAction)
resp, err := http.Post("https://iotexscout.io/apiproxy/api.ActionsService.GetAllActionsByAddress", "application/json",
Liuhaai marked this conversation as resolved.
Show resolved Hide resolved
bytes.NewBuffer(jsonData))
if err != nil {
sta, ok := status.FromError(err)
if ok {
return output.NewError(output.APIError, sta.Message(), nil)
}
return output.NewError(output.NetworkError, "failed to invoke GetActions api", err)
return output.NewError(output.NetworkError, "failed to send request", nil)
}
if len(response.ActionInfo) == 0 {
return output.NewError(output.APIError, "no action info returned", nil)

var respData allActionsByAddressResponse
err = json.NewDecoder(resp.Body).Decode(&respData)
if err != nil {
return output.NewError(output.SerializationError, "failed to deserialize the response", nil)
}
actions := respData.Results

fmt.Println("Total:", len(actions))
showFields := []interface{}{
"Hash",
"Time",
"Status",
"ActHash",
"TimeStamp",
"BlkHeight",
"ActCategory",
"ActType",
"Sender",
"Type",
"To",
"Contract",
"Recipient",
"Amount",
}
tbl := table.New(showFields...)

for i := range response.ActionInfo {
k := len(response.ActionInfo) - 1 - i
actionInfo := response.ActionInfo[k]
requestGetReceipt := &iotexapi.GetReceiptByActionRequest{ActionHash: actionInfo.ActHash}
responseReceipt, err := cli.GetReceiptByAction(ctx, requestGetReceipt)
if err != nil {
sta, ok := status.FromError(err)
if ok {
fmt.Println(output.NewError(output.APIError, sta.Message(), nil))
} else {
fmt.Println(output.NewError(output.NetworkError, "failed to invoke GetReceiptByAction api", err))
}
continue
}
amount := "0"
transfer := actionInfo.Action.Core.GetTransfer()
if transfer != nil {
amount, _ = util.StringToIOTX(transfer.Amount)

}
tbl.AddRow(
tb := table.New(showFields...)
for _, actionInfo := range actions {
tb.AddRow(
actionInfo.ActHash,
getActionTime(actionInfo),
iotextypes.ReceiptStatus_name[int32(responseReceipt.ReceiptInfo.Receipt.GetStatus())],
actionInfo.TimeStamp,
actionInfo.BlkHeight,
actionInfo.RecordType,
actionInfo.ActType,
actionInfo.Sender,
getActionTypeString(actionInfo),
getActionTo(actionInfo),
getActionContract(responseReceipt),
amount+" IOTX",
actionInfo.Recipient,
actionInfo.Amount+" IOTX",
)
}
tbl.Print()
tb.Print()
return nil
}

func getActionContract(responseReceipt *iotexapi.GetReceiptByActionResponse) string {
contract := responseReceipt.ReceiptInfo.Receipt.GetContractAddress()
if contract == "" {
contract = "-"
}
return contract
}

func getActionTypeString(actionInfo *iotexapi.ActionInfo) string {
actionType := fmt.Sprintf("%T", actionInfo.Action.Core.GetAction())
return strings.TrimLeft(actionType, "*iotextypes.ActionCore_")
}

func getActionTo(actionInfo *iotexapi.ActionInfo) string {
recipient := ""
switch getActionTypeString(actionInfo) {
case "Transfer":
transfer := actionInfo.Action.Core.GetTransfer()
recipient = transfer.GetRecipient()
case "Execution":
execution := actionInfo.Action.Core.GetExecution()
recipient = execution.GetContract()
}
if recipient == "" {
recipient = "-"
}
return recipient
}

func getActionTime(actionInfo *iotexapi.ActionInfo) string {
if actionInfo.Timestamp != nil {
if ts, err := ptypes.Timestamp(actionInfo.Timestamp); err == nil {
return ts.String()
}
}
return ""
}