From 534ccbbf40bec1dd6e2a2034154b828064aaacd2 Mon Sep 17 00:00:00 2001 From: millken Date: Fri, 15 Jul 2022 00:05:14 +0800 Subject: [PATCH 1/6] [test] fix TestLoadBlockchainfromDB (#3521) * Fix #3520 --- blockchain/integrity/integrity_test.go | 19 +++++++++---------- blockchain/pubsubmanager.go | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/blockchain/integrity/integrity_test.go b/blockchain/integrity/integrity_test.go index a635aa6aa7..8bf890d14a 100644 --- a/blockchain/integrity/integrity_test.go +++ b/blockchain/integrity/integrity_test.go @@ -11,7 +11,7 @@ import ( "encoding/hex" "fmt" "math/big" - "sync" + "sync/atomic" "testing" "time" @@ -832,22 +832,17 @@ func TestBlockchain_MintNewBlock_PopAccount(t *testing.T) { } type MockSubscriber struct { - counter int - mu sync.RWMutex + counter int32 } func (ms *MockSubscriber) ReceiveBlock(blk *block.Block) error { - ms.mu.Lock() tsfs, _ := classifyActions(blk.Actions) - ms.counter += len(tsfs) - ms.mu.Unlock() + atomic.AddInt32(&ms.counter, int32(len(tsfs))) return nil } func (ms *MockSubscriber) Counter() int { - ms.mu.RLock() - defer ms.mu.RUnlock() - return ms.counter + return int(atomic.LoadInt32(&ms.counter)) } func TestConstantinople(t *testing.T) { @@ -1142,8 +1137,12 @@ func TestLoadBlockchainfromDB(t *testing.T) { height := bc.TipHeight() fmt.Printf("Open blockchain pass, height = %d\n", height) require.NoError(addTestingTsfBlocks(cfg, bc, dao, ap)) + //make sure pubsub is completed + err = testutil.WaitUntil(200*time.Millisecond, 3*time.Second, func() (bool, error) { + return 24 == ms.Counter(), nil + }) + require.NoError(err) require.NoError(bc.Stop(ctx)) - require.Equal(24, ms.Counter()) // Load a blockchain from DB bc = blockchain.NewBlockchain( diff --git a/blockchain/pubsubmanager.go b/blockchain/pubsubmanager.go index 5c251174e4..1503e1bbcd 100644 --- a/blockchain/pubsubmanager.go +++ b/blockchain/pubsubmanager.go @@ -93,8 +93,8 @@ func (ps *pubSub) RemoveBlockListener(s BlockCreationSubscriber) error { // SendBlockToSubscribers sends block to every subscriber by using buffer channel func (ps *pubSub) SendBlockToSubscribers(blk *block.Block) { - ps.lock.Lock() - defer ps.lock.Unlock() + ps.lock.RLock() + defer ps.lock.RUnlock() for _, elem := range ps.blocklisteners { elem.pendingBlksBuffer <- blk } From bfbaecef912f09f40052f93f9457a804c04406c5 Mon Sep 17 00:00:00 2001 From: huofei <68298506@qq.com> Date: Fri, 15 Jul 2022 01:28:56 +0800 Subject: [PATCH 2/6] [action] fix incorrect conversion between integer types (#3545) Co-authored-by: Haaai <55118568+Liuhaai@users.noreply.github.com> --- action/consignment_transfer.go | 8 ++++---- action/protocol/staking/handlers_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/action/consignment_transfer.go b/action/consignment_transfer.go index 264e9bfe68..d8115112f6 100644 --- a/action/consignment_transfer.go +++ b/action/consignment_transfer.go @@ -57,8 +57,8 @@ type ( // ConsignMsgEther is the consignment message format of Ethereum ConsignMsgEther struct { - BucketIdx int `json:"bucket"` - Nonce int `json:"nonce"` + BucketIdx uint64 `json:"bucket"` + Nonce uint64 `json:"nonce"` Recipient string `json:"recipient"` Reclaim string `json:"reclaim"` } @@ -148,8 +148,8 @@ func NewConsignMsg(sigType, recipient string, bucketIdx, nonce uint64) ([]byte, switch sigType { case "Ethereum": msg := ConsignMsgEther{ - BucketIdx: int(bucketIdx), - Nonce: int(nonce), + BucketIdx: bucketIdx, + Nonce: nonce, Recipient: recipient, Reclaim: _reclaim, } diff --git a/action/protocol/staking/handlers_test.go b/action/protocol/staking/handlers_test.go index 8f5231c0db..1b9727afe5 100644 --- a/action/protocol/staking/handlers_test.go +++ b/action/protocol/staking/handlers_test.go @@ -2057,7 +2057,7 @@ func TestProtocol_HandleConsignmentTransfer(t *testing.T) { // transfer to test.to through consignment var consign []byte if !test.nilPayload { - consign = newconsignment(require, int(test.sigIndex), int(test.sigNonce), test.bucketOwner, test.to.String(), test.consignType, test.reclaim, test.wrongSig) + consign = newconsignment(require, test.sigIndex, test.sigNonce, test.bucketOwner, test.to.String(), test.consignType, test.reclaim, test.wrongSig) } act, err := action.NewTransferStake(1, caller.String(), 0, consign, gasLimit, gasPrice) @@ -2713,7 +2713,7 @@ func depositGas(ctx context.Context, sm protocol.StateManager, gasFee *big.Int) return nil, accountutil.StoreAccount(sm, actionCtx.Caller, acc) } -func newconsignment(r *require.Assertions, bucketIdx, nonce int, senderPrivate, recipient, consignTpye, reclaim string, wrongSig bool) []byte { +func newconsignment(r *require.Assertions, bucketIdx, nonce uint64, senderPrivate, recipient, consignTpye, reclaim string, wrongSig bool) []byte { msg := action.ConsignMsgEther{ BucketIdx: bucketIdx, Nonce: nonce, From ad61fa376bdb5ae571da5eb7d9eedbb4121fa3f1 Mon Sep 17 00:00:00 2001 From: huofei <68298506@qq.com> Date: Fri, 15 Jul 2022 07:12:03 +0800 Subject: [PATCH 3/6] [ioctl] Incorrect conversion between integer types (#3522) Incorrect conversion between integer types --- ioctl/cmd/bc/bcbucketlist.go | 12 +++++++----- ioctl/newcmd/bc/bcbucketlist.go | 17 ++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/ioctl/cmd/bc/bcbucketlist.go b/ioctl/cmd/bc/bcbucketlist.go index c1fc6526cf..4a73035075 100644 --- a/ioctl/cmd/bc/bcbucketlist.go +++ b/ioctl/cmd/bc/bcbucketlist.go @@ -87,24 +87,26 @@ func (m *bucketlistMessage) String() string { // getBucketList get bucket list from chain func getBucketList(method, addr string, args ...string) (err error) { - offset, limit := uint64(0), uint64(1000) + offset, limit := uint32(0), uint32(1000) if len(args) > 0 { - offset, err = strconv.ParseUint(args[0], 10, 64) + val, err := strconv.ParseUint(args[0], 10, 32) if err != nil { return output.NewError(output.ValidationError, "invalid offset", err) } + offset = uint32(val) } if len(args) > 1 { - limit, err = strconv.ParseUint(args[1], 10, 64) + val, err := strconv.ParseUint(args[1], 10, 32) if err != nil { return output.NewError(output.ValidationError, "invalid limit", err) } + limit = uint32(val) } switch method { case _bucketlistMethodByVoter: - return getBucketListByVoter(addr, uint32(offset), uint32(limit)) + return getBucketListByVoter(addr, offset, limit) case _bucketlistMethodByCandidate: - return getBucketListByCand(addr, uint32(offset), uint32(limit)) + return getBucketListByCand(addr, offset, limit) } return output.NewError(output.InputError, "unknown ", nil) } diff --git a/ioctl/newcmd/bc/bcbucketlist.go b/ioctl/newcmd/bc/bcbucketlist.go index 6bb4f07ac9..1b4fcaaadc 100644 --- a/ioctl/newcmd/bc/bcbucketlist.go +++ b/ioctl/newcmd/bc/bcbucketlist.go @@ -65,37 +65,40 @@ func NewBCBucketListCmd(client ioctl.Client) *cobra.Command { err error ) - offset, limit := uint64(0), uint64(1000) - method, addr := args[0], args[1] - s := args[2:] + offset, limit := uint32(0), uint32(1000) + method, addr, s := args[0], args[1], args[2:] if len(s) > 0 { - offset, err = strconv.ParseUint(s[0], 10, 64) + val, err := strconv.ParseUint(s[0], 10, 32) if err != nil { return errors.Wrap(err, "invalid offset") } + offset = uint32(val) } if len(s) > 1 { - limit, err = strconv.ParseUint(s[1], 10, 64) + val, err := strconv.ParseUint(s[1], 10, 32) if err != nil { return errors.Wrap(err, "invalid limit") } + limit = uint32(val) } + switch method { case MethodVoter: address, err = client.AddressWithDefaultIfNotExist(addr) if err != nil { return err } - bl, err = getBucketListByVoterAddress(client, address, uint32(offset), uint32(limit)) + bl, err = getBucketListByVoterAddress(client, address, offset, limit) case MethodCandidate: - bl, err = getBucketListByCandidateName(client, addr, uint32(offset), uint32(limit)) + bl, err = getBucketListByCandidateName(client, addr, offset, limit) default: return errors.New("unknown ") } if err != nil { return err } + var lines []string if len(bl.Buckets) == 0 { lines = append(lines, "Empty bucketlist with given address") From c1866c0be66037b34c544eabe835fcc5043aedb8 Mon Sep 17 00:00:00 2001 From: huofei <68298506@qq.com> Date: Sat, 16 Jul 2022 02:34:56 +0800 Subject: [PATCH 4/6] fix potential file inclusion via variable (#3549) --- e2etest/util.go | 3 ++- ioctl/client.go | 3 ++- ioctl/cmd/account/account.go | 2 +- ioctl/cmd/contract/contract.go | 3 ++- ioctl/cmd/contract/contractshare.go | 4 ++-- ioctl/cmd/hdwallet/hdwalletderive.go | 3 ++- ioctl/config/config.go | 3 ++- ioctl/doc/doc.go | 2 +- ioctl/util/util.go | 3 ++- pkg/recovery/recovery.go | 2 +- state/factory/patchstore.go | 9 +++++---- tools/actioninjector.v2/internal/cmd/inject.go | 3 ++- tools/util/injectorutil.go | 3 ++- 13 files changed, 26 insertions(+), 17 deletions(-) diff --git a/e2etest/util.go b/e2etest/util.go index 6592871cf9..b6d6433d1b 100644 --- a/e2etest/util.go +++ b/e2etest/util.go @@ -11,6 +11,7 @@ import ( "encoding/hex" "math/big" "os" + "path/filepath" "github.com/iotexproject/go-pkgs/hash" "github.com/pkg/errors" @@ -194,7 +195,7 @@ func addTestingTsfBlocks(bc blockchain.Blockchain, ap actpool.ActPool) error { } func copyDB(srcDB, dstDB string) error { - input, err := os.ReadFile(srcDB) + input, err := os.ReadFile(filepath.Clean(srcDB)) if err != nil { return errors.Wrap(err, "failed to read source db file") } diff --git a/ioctl/client.go b/ioctl/client.go index b7921efebc..3d2e739f9a 100644 --- a/ioctl/client.go +++ b/ioctl/client.go @@ -17,6 +17,7 @@ import ( "net/http" "os" "os/exec" + "path/filepath" "strings" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -257,7 +258,7 @@ func (c *client) NewKeyStore() *keystore.KeyStore { } func (c *client) DecryptPrivateKey(passwordOfKeyStore, keyStorePath string) (*ecdsa.PrivateKey, error) { - keyJSON, err := os.ReadFile(keyStorePath) + keyJSON, err := os.ReadFile(filepath.Clean(keyStorePath)) if err != nil { return nil, fmt.Errorf("keystore file \"%s\" read error", keyStorePath) } diff --git a/ioctl/cmd/account/account.go b/ioctl/cmd/account/account.go index 31c35a772b..6462d6c668 100644 --- a/ioctl/cmd/account/account.go +++ b/ioctl/cmd/account/account.go @@ -306,7 +306,7 @@ func newAccountByKey(alias string, privateKey string, walletDir string) (string, } func newAccountByKeyStore(alias, passwordOfKeyStore, keyStorePath string, walletDir string) (string, error) { - keyJSON, err := os.ReadFile(keyStorePath) + keyJSON, err := os.ReadFile(filepath.Clean(keyStorePath)) if err != nil { return "", output.NewError(output.ReadFileError, fmt.Sprintf("keystore file \"%s\" read error", keyStorePath), nil) diff --git a/ioctl/cmd/contract/contract.go b/ioctl/cmd/contract/contract.go index 17f042f85c..8b71f96cbe 100644 --- a/ioctl/cmd/contract/contract.go +++ b/ioctl/cmd/contract/contract.go @@ -10,6 +10,7 @@ import ( "encoding/hex" "fmt" "os" + "path/filepath" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common/compiler" @@ -101,7 +102,7 @@ func checkCompilerVersion(solc *compiler.Solidity) bool { } func readAbiFile(abiFile string) (*abi.ABI, error) { - abiBytes, err := os.ReadFile(abiFile) + abiBytes, err := os.ReadFile(filepath.Clean(abiFile)) if err != nil { return nil, output.NewError(output.ReadFileError, "failed to read abi file", err) } diff --git a/ioctl/cmd/contract/contractshare.go b/ioctl/cmd/contract/contractshare.go index 5fcbc769ad..704251410c 100644 --- a/ioctl/cmd/contract/contractshare.go +++ b/ioctl/cmd/contract/contractshare.go @@ -92,7 +92,7 @@ func isDir(path string) bool { func isReadOnly(path string) bool { var readOnly = false - file, err := os.OpenFile(path, os.O_WRONLY, 0666) + file, err := os.OpenFile(filepath.Clean(path), os.O_WRONLY, 0666) if err != nil { if os.IsPermission(err) { log.Println("Error: Write permission denied.") @@ -198,7 +198,7 @@ func share(args []string) error { t := request.Payload getPayload := reflect.ValueOf(t).Index(0).Interface().(map[string]interface{}) getPayloadPath := getPayload["path"].(string) - upload, err := os.ReadFile(_givenPath + "/" + getPayloadPath) + upload, err := os.ReadFile(filepath.Clean(_givenPath + "/" + getPayloadPath)) if err != nil { log.Println("read file failed: ", err) } diff --git a/ioctl/cmd/hdwallet/hdwalletderive.go b/ioctl/cmd/hdwallet/hdwalletderive.go index 44d0ec23c3..4c5b3a2211 100644 --- a/ioctl/cmd/hdwallet/hdwalletderive.go +++ b/ioctl/cmd/hdwallet/hdwalletderive.go @@ -10,6 +10,7 @@ import ( "bytes" "fmt" "os" + "path/filepath" ecrypt "github.com/ethereum/go-ethereum/crypto" hdwallet "github.com/miguelmota/go-ethereum-hdwallet" @@ -71,7 +72,7 @@ func DeriveKey(account, change, index uint32, password string) (string, crypto.P return "", nil, output.NewError(output.InputError, "Run 'ioctl hdwallet create' to create your HDWallet first.", nil) } - enctxt, err := os.ReadFile(hdWalletConfigFile) + enctxt, err := os.ReadFile(filepath.Clean(hdWalletConfigFile)) if err != nil { return "", nil, output.NewError(output.InputError, "failed to read config", err) } diff --git a/ioctl/config/config.go b/ioctl/config/config.go index 228a0a66e7..d1e3ba2bde 100644 --- a/ioctl/config/config.go +++ b/ioctl/config/config.go @@ -9,6 +9,7 @@ package config import ( "fmt" "os" + "path/filepath" "github.com/spf13/cobra" "gopkg.in/yaml.v2" @@ -137,7 +138,7 @@ func LoadConfig() (Config, error) { ReadConfig := Config{ Aliases: make(map[string]string), } - in, err := os.ReadFile(DefaultConfigFile) + in, err := os.ReadFile(filepath.Clean(DefaultConfigFile)) if err == nil { if err := yaml.Unmarshal(in, &ReadConfig); err != nil { return ReadConfig, err diff --git a/ioctl/doc/doc.go b/ioctl/doc/doc.go index 2a7cebed66..d887ccf4bc 100644 --- a/ioctl/doc/doc.go +++ b/ioctl/doc/doc.go @@ -32,7 +32,7 @@ func GenMarkdownTreeCustom(c *cobra.Command, dir string, name string, path strin filename = filepath.Join(path, "README.md") } - f, err := os.Create(filename) + f, err := os.Create(filepath.Clean(filename)) if err != nil { return err } diff --git a/ioctl/util/util.go b/ioctl/util/util.go index e2251ebbf2..fcf1f394a8 100644 --- a/ioctl/util/util.go +++ b/ioctl/util/util.go @@ -13,6 +13,7 @@ import ( "math/big" "os" "os/signal" + "path/filepath" "strconv" "strings" "syscall" @@ -169,7 +170,7 @@ func Address(in string) (string, error) { // JwtAuth used for ioctl set auth and send for every grpc request func JwtAuth() (jwt metadata.MD, err error) { jwtFile := os.Getenv("HOME") + "/.config/ioctl/default/auth.jwt" - jwtString, err := os.ReadFile(jwtFile) + jwtString, err := os.ReadFile(filepath.Clean(jwtFile)) if err != nil { return nil, err } diff --git a/pkg/recovery/recovery.go b/pkg/recovery/recovery.go index a2612817d3..3d2d4fe957 100644 --- a/pkg/recovery/recovery.go +++ b/pkg/recovery/recovery.go @@ -79,7 +79,7 @@ func LogCrash(r interface{}) { } func writeHeapProfile(path string) { - f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) + f, err := os.OpenFile(filepath.Clean(path), os.O_CREATE|os.O_RDWR, 0644) if err != nil { log.S().Errorf("crashlog: open heap profile error: %v", err) return diff --git a/state/factory/patchstore.go b/state/factory/patchstore.go index 064d884a88..a48d665090 100644 --- a/state/factory/patchstore.go +++ b/state/factory/patchstore.go @@ -11,6 +11,7 @@ import ( "encoding/hex" "io" "os" + "path/filepath" "strconv" "github.com/pkg/errors" @@ -43,16 +44,16 @@ type ( * key: hex string * value: hex string */ -func newPatchStore(filepath string) (*patchStore, error) { +func newPatchStore(fpath string) (*patchStore, error) { store := &patchStore{ patchs: map[uint64][]*patch{}, } - if filepath == "" { + if fpath == "" { return store, nil } - file, err := os.Open(filepath) + file, err := os.Open(filepath.Clean(fpath)) if err != nil { - return nil, errors.Wrapf(err, "failed to open kvstore patch, %s", filepath) + return nil, errors.Wrapf(err, "failed to open kvstore patch, %s", fpath) } reader := csv.NewReader(file) reader.FieldsPerRecord = -1 diff --git a/tools/actioninjector.v2/internal/cmd/inject.go b/tools/actioninjector.v2/internal/cmd/inject.go index 3ac37d0733..1a03076938 100644 --- a/tools/actioninjector.v2/internal/cmd/inject.go +++ b/tools/actioninjector.v2/internal/cmd/inject.go @@ -16,6 +16,7 @@ import ( "math/big" "math/rand" "os" + "path/filepath" "strings" "sync" "time" @@ -112,7 +113,7 @@ func (p *injectProcessor) randAccounts(num int) error { } func (p *injectProcessor) loadAccounts(keypairsPath string) error { - keyPairBytes, err := os.ReadFile(keypairsPath) + keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath)) if err != nil { return errors.Wrap(err, "failed to read key pairs file") } diff --git a/tools/util/injectorutil.go b/tools/util/injectorutil.go index 59c9825193..ba650c6a79 100644 --- a/tools/util/injectorutil.go +++ b/tools/util/injectorutil.go @@ -12,6 +12,7 @@ import ( "math/big" "math/rand" "os" + "path/filepath" "sync" "sync/atomic" "time" @@ -81,7 +82,7 @@ func GetTotalTsfFailed() uint64 { // LoadAddresses loads key pairs from key pair path and construct addresses func LoadAddresses(keypairsPath string, chainID uint32) ([]*AddressKey, error) { // Load Senders' public/private key pairs - keyPairBytes, err := os.ReadFile(keypairsPath) + keyPairBytes, err := os.ReadFile(filepath.Clean(keypairsPath)) if err != nil { return nil, errors.Wrap(err, "failed to read key pairs file") } From 7783ced927bf5008c47d059252ab525c561f8f16 Mon Sep 17 00:00:00 2001 From: Jeremy Chou Date: Sat, 16 Jul 2022 09:08:49 +0800 Subject: [PATCH 5/6] [ioctl] Build action command line into new ioctl (#3472) * [ioctl] build action command line into new ioctl --- ioctl/newcmd/action/action.go | 500 +++++++++++++++++++++++++++++ ioctl/newcmd/action/action_test.go | 127 ++++++++ 2 files changed, 627 insertions(+) create mode 100644 ioctl/newcmd/action/action.go create mode 100644 ioctl/newcmd/action/action_test.go diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go new file mode 100644 index 0000000000..802aa14752 --- /dev/null +++ b/ioctl/newcmd/action/action.go @@ -0,0 +1,500 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "context" + "encoding/hex" + "math/big" + "strings" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "go.uber.org/zap" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/action" + "github.com/iotexproject/iotex-core/ioctl" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/flag" + "github.com/iotexproject/iotex-core/ioctl/newcmd/account" + "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" + "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/pkg/log" + "github.com/iotexproject/iotex-core/pkg/util/byteutil" +) + +// Multi-language support +var ( + _actionCmdShorts = map[config.Language]string{ + config.English: "Manage actions of IoTeX blockchain", + config.Chinese: "管理IoTex区块链的行为", // this translation + } + _infoWarn = map[config.Language]string{ + config.English: "** This is an irreversible action!\n" + + "Once an account is deleted, all the assets under this account may be lost!\n" + + "Type 'YES' to continue, quit for anything else.", + config.Chinese: "** 这是一个不可逆转的操作!\n" + + "一旦一个账户被删除, 该账户下的所有资源都可能会丢失!\n" + + "输入 'YES' 以继续, 否则退出", + } + _infoQuit = map[config.Language]string{ + config.English: "quit", + config.Chinese: "退出", + } + _flagGasLimitUsages = map[config.Language]string{ + config.English: "set gas limit", + config.Chinese: "设置燃气上限", + } + _flagGasPriceUsages = map[config.Language]string{ + config.English: `set gas price (unit: 10^(-6)IOTX), use suggested gas price if input is "0"`, + config.Chinese: `设置燃气费(单位:10^(-6)IOTX),如果输入为「0」,则使用默认燃气费`, + } + _flagNonceUsages = map[config.Language]string{ + config.English: "set nonce (default using pending nonce)", + config.Chinese: "设置 nonce (默认使用 pending nonce)", + } + _flagSignerUsages = map[config.Language]string{ + config.English: "choose a signing account", + config.Chinese: "选择要签名的帐户", + } + _flagBytecodeUsages = map[config.Language]string{ + config.English: "set the byte code", + config.Chinese: "设置字节码", + } + _flagAssumeYesUsages = map[config.Language]string{ + config.English: "answer yes for all confirmations", + config.Chinese: "为所有确认设置 yes", + } + _flagPasswordUsages = map[config.Language]string{ + config.English: "input password for account", + config.Chinese: "设置密码", + } +) + +// Flag label, short label and defaults +const ( + gasLimitFlagLabel = "gas-limit" + gasLimitFlagShortLabel = "l" + gasLimitFlagDefault = uint64(20000000) + gasPriceFlagLabel = "gas-price" + gasPriceFlagShortLabel = "p" + gasPriceFlagDefault = "1" + nonceFlagLabel = "nonce" + nonceFlagShortLabel = "n" + nonceFlagDefault = uint64(0) + signerFlagLabel = "signer" + signerFlagShortLabel = "s" + signerFlagDefault = "" + bytecodeFlagLabel = "bytecode" + bytecodeFlagShortLabel = "b" + bytecodeFlagDefault = "" + assumeYesFlagLabel = "assume-yes" + assumeYesFlagShortLabel = "y" + assumeYesFlagDefault = false + passwordFlagLabel = "password" + passwordFlagShortLabel = "P" + passwordFlagDefault = "" +) + +func registerGasLimitFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagGasLimitUsages) + flag.NewUint64VarP(gasLimitFlagLabel, gasLimitFlagShortLabel, gasLimitFlagDefault, usage).RegisterCommand(cmd) +} + +func registerGasPriceFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagGasPriceUsages) + flag.NewStringVarP(gasPriceFlagLabel, gasPriceFlagShortLabel, gasPriceFlagDefault, usage).RegisterCommand(cmd) +} + +func registerNonceFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagNonceUsages) + flag.NewUint64VarP(nonceFlagLabel, nonceFlagShortLabel, nonceFlagDefault, usage).RegisterCommand(cmd) +} + +func registerSignerFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagSignerUsages) + flag.NewStringVarP(signerFlagLabel, signerFlagShortLabel, signerFlagDefault, usage).RegisterCommand(cmd) +} + +func registerBytecodeFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagBytecodeUsages) + flag.NewStringVarP(bytecodeFlagLabel, bytecodeFlagShortLabel, bytecodeFlagDefault, usage).RegisterCommand(cmd) +} + +func registerAssumeYesFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagAssumeYesUsages) + flag.BoolVarP(assumeYesFlagLabel, assumeYesFlagShortLabel, assumeYesFlagDefault, usage).RegisterCommand(cmd) +} + +func registerPasswordFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagPasswordUsages) + flag.NewStringVarP(passwordFlagLabel, passwordFlagShortLabel, passwordFlagDefault, usage).RegisterCommand(cmd) +} + +func mustString(v string, err error) string { + if err != nil { + log.L().Panic("input flag must be string", zap.Error(err)) + } + return v +} + +func mustUint64(v uint64, err error) uint64 { + if err != nil { + log.L().Panic("input flag must be uint64", zap.Error(err)) + } + return v +} + +func mustBoolean(v bool, err error) bool { + if err != nil { + log.L().Panic("input flag must be boolean", zap.Error(err)) + } + return v +} + +func gasLimitFlagValue(cmd *cobra.Command) (v uint64) { + return mustUint64(cmd.Flags().GetUint64(gasLimitFlagLabel)) +} + +func gasPriceFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(gasPriceFlagLabel)) +} + +func getNonceFlagValue(cmd *cobra.Command) (v uint64) { + return mustUint64(cmd.Flags().GetUint64(nonceFlagLabel)) +} + +func getSignerFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(signerFlagLabel)) +} + +func getBytecodeFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(bytecodeFlagLabel)) +} + +func getDecodeBytecode(cmd *cobra.Command) ([]byte, error) { + return hex.DecodeString(util.TrimHexPrefix(getBytecodeFlagValue(cmd))) +} + +func getAssumeYesFlagValue(cmd *cobra.Command) (v bool) { + return mustBoolean(cmd.Flags().GetBool(assumeYesFlagLabel)) +} + +func getPasswordFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(passwordFlagLabel)) +} + +func selectTranslation(client ioctl.Client, trls map[config.Language]string) string { + txt, _ := client.SelectTranslation(trls) + return txt +} + +// NewActionCmd represents the action command +func NewActionCmd(client ioctl.Client) *cobra.Command { + ac := &cobra.Command{ + Use: "action", + Short: selectTranslation(client, _actionCmdShorts), + } + + // TODO add sub commands + // cmd.AddCommand(NewActionHash(client)) + // cmd.AddCommand(NewActionTransfer(client)) + // cmd.AddCommand(NewActionDeploy(client)) + // cmd.AddCommand(NewActionInvoke(client)) + // cmd.AddCommand(NewActionRead(client)) + // cmd.AddCommand(NewActionClaim(client)) + // cmd.AddCommand(NewActionDeposit(client)) + // cmd.AddCommand(NewActionSendRaw(client)) + + client.SetEndpointWithFlag(ac.PersistentFlags().StringVar) + client.SetInsecureWithFlag(ac.PersistentFlags().BoolVar) + + return ac +} + +// RegisterWriteCommand registers action flags for command +func RegisterWriteCommand(client ioctl.Client, cmd *cobra.Command) { + registerGasLimitFlag(client, cmd) + registerGasPriceFlag(client, cmd) + registerSignerFlag(client, cmd) + registerNonceFlag(client, cmd) + registerAssumeYesFlag(client, cmd) + registerPasswordFlag(client, cmd) +} + +func handleClientRequestError(err error, apiName string) error { + sta, ok := status.FromError(err) + if ok { + return errors.New(sta.Message()) + } + return errors.Wrapf(err, "failed to invoke %s api", apiName) +} + +// Signer returns signer's address +func Signer(client ioctl.Client, cmd *cobra.Command) (address string, err error) { + addressOrAlias := getSignerFlagValue(cmd) + if util.AliasIsHdwalletKey(addressOrAlias) { + return addressOrAlias, nil + } + return client.AddressWithDefaultIfNotExist(addressOrAlias) +} + +func nonce(client ioctl.Client, cmd *cobra.Command, executor string) (uint64, error) { + if util.AliasIsHdwalletKey(executor) { + // for hdwallet key, get the nonce in SendAction() + return 0, nil + } + if nonce := getNonceFlagValue(cmd); nonce != 0 { + return nonce, nil + } + accountMeta, err := account.Meta(client, executor) + if err != nil { + return 0, errors.Wrap(err, "failed to get account meta") + } + return accountMeta.PendingNonce, nil +} + +// gasPriceInRau returns the suggest gas price +func gasPriceInRau(client ioctl.Client, cmd *cobra.Command) (*big.Int, error) { + if client.IsCryptoSm2() { + return big.NewInt(0), nil + } + gasPrice := gasPriceFlagValue(cmd) + if len(gasPrice) != 0 { + return util.StringToRau(gasPrice, util.GasPriceDecimalNum) + } + + cli, err := client.APIServiceClient() + if err != nil { + return nil, errors.Wrap(err, "failed to connect to endpoint") + } + + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + rsp, err := cli.SuggestGasPrice(ctx, &iotexapi.SuggestGasPriceRequest{}) + if err != nil { + return nil, handleClientRequestError(err, "SuggestGasPrice") + } + return new(big.Int).SetUint64(rsp.GasPrice), nil +} + +func fixGasLimit(client ioctl.Client, caller string, execution *action.Execution) (*action.Execution, error) { + cli, err := client.APIServiceClient() + if err != nil { + return nil, errors.Wrap(err, "failed to connect to endpoint") + } + + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + res, err := cli.EstimateActionGasConsumption(ctx, + &iotexapi.EstimateActionGasConsumptionRequest{ + Action: &iotexapi.EstimateActionGasConsumptionRequest_Execution{ + Execution: execution.Proto(), + }, + CallerAddress: caller, + }) + if err != nil { + return nil, handleClientRequestError(err, "EstimateActionGasConsumption") + } + return action.NewExecution(execution.Contract(), execution.Nonce(), execution.Amount(), res.Gas, execution.GasPrice(), execution.Data()) +} + +// SendRaw sends raw action to blockchain +func SendRaw(client ioctl.Client, cmd *cobra.Command, selp *iotextypes.Action) error { + cli, err := client.APIServiceClient() + if err != nil { + return errors.Wrap(err, "failed to connect to endpoint") + } + + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + _, err = cli.SendAction(ctx, &iotexapi.SendActionRequest{Action: selp}) + if err != nil { + return handleClientRequestError(err, "SendAction") + } + + shash := hash.Hash256b(byteutil.Must(proto.Marshal(selp))) + txhash := hex.EncodeToString(shash[:]) + URL := "https://" + endpoint := client.Config().Endpoint + explorer := client.Config().Explorer + switch explorer { + case "iotexscan": + if strings.Contains(endpoint, "testnet") { + URL += "testnet." + } + URL += "iotexscan.io/action/" + txhash + case "iotxplorer": + URL = "iotxplorer.io/actions/" + txhash + default: + URL = explorer + txhash + } + cmd.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s\n", URL) + return nil +} + +// SendAction sends signed action to blockchain +func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, signer string) error { + sk, err := account.PrivateKeyFromSigner(client, cmd, signer, getPasswordFlagValue(cmd)) + if err != nil { + return err + } + + chainMeta, err := bc.GetChainMeta(client) + if err != nil { + return errors.Wrap(err, "failed to get chain meta") + } + elp.SetChainID(chainMeta.GetChainID()) + + if util.AliasIsHdwalletKey(signer) { + addr := sk.PublicKey().Address() + signer = addr.String() + nonce, err := nonce(client, cmd, signer) + if err != nil { + return errors.Wrap(err, "failed to get nonce ") + } + elp.SetNonce(nonce) + } + + sealed, err := action.Sign(elp, sk) + if err != nil { + return errors.Wrap(err, "failed to sign action") + } + if err := isBalanceEnough(client, signer, sealed); err != nil { + return errors.Wrap(err, "failed to pass balance check") + } + + selp := sealed.Proto() + sk.Zero() + // TODO wait newcmd/action/actionhash impl pr #3425 + // actionInfo, err := printActionProto(selp) + // if err != nil { + // return errors.Wrap(err, "failed to print action proto message") + // } + // cmd.Println(actionInfo) + + if !getAssumeYesFlagValue(cmd) { + infoWarn := selectTranslation(client, _infoWarn) + infoQuit := selectTranslation(client, _infoQuit) + if !client.AskToConfirm(infoWarn) { + cmd.Println(infoQuit) + } + return nil + } + + return SendRaw(client, cmd, selp) +} + +// Execute sends signed execution transaction to blockchain +func Execute(client ioctl.Client, cmd *cobra.Command, contract string, amount *big.Int, bytecode []byte) error { + if len(contract) == 0 && len(bytecode) == 0 { + return errors.New("failed to deploy contract with empty bytecode") + } + gasPriceRau, err := gasPriceInRau(client, cmd) + if err != nil { + return errors.Wrap(err, "failed to get gas price") + } + signer, err := Signer(client, cmd) + if err != nil { + return errors.Wrap(err, "failed to get signer address") + } + nonce, err := nonce(client, cmd, signer) + if err != nil { + return errors.Wrap(err, "failed to get nonce") + } + gasLimit := gasLimitFlagValue(cmd) + tx, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPriceRau, bytecode) + if err != nil || tx == nil { + return errors.Wrap(err, "failed to make a Execution instance") + } + if gasLimit == 0 { + tx, err = fixGasLimit(client, signer, tx) + if err != nil || tx == nil { + return errors.Wrap(err, "failed to fix Execution gas limit") + } + gasLimit = tx.GasLimit() + } + return SendAction( + client, + cmd, + (&action.EnvelopeBuilder{}). + SetNonce(nonce). + SetGasPrice(gasPriceRau). + SetGasLimit(gasLimit). + SetAction(tx).Build(), + signer, + ) +} + +// Read reads smart contract on IoTeX blockchain +func Read(client ioctl.Client, cmd *cobra.Command, contract address.Address, amount string, bytecode []byte) (string, error) { + cli, err := client.APIServiceClient() + if err != nil { + return "", errors.Wrap(err, "failed to connect to endpoint") + } + + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + callerAddr, _ := Signer(client, cmd) + if callerAddr == "" { + callerAddr = address.ZeroAddress + } + + res, err := cli.ReadContract(ctx, + &iotexapi.ReadContractRequest{ + Execution: &iotextypes.Execution{ + Amount: amount, + Contract: contract.String(), + Data: bytecode, + }, + CallerAddress: callerAddr, + GasLimit: gasLimitFlagValue(cmd), + }, + ) + if err != nil { + return "", handleClientRequestError(err, "ReadContract") + } + return res.Data, nil +} + +func isBalanceEnough(client ioctl.Client, address string, act action.SealedEnvelope) error { + accountMeta, err := account.Meta(client, address) + if err != nil { + return errors.Wrap(err, "failed to get account meta") + } + balance, ok := new(big.Int).SetString(accountMeta.Balance, 10) + if !ok { + return errors.New("failed to convert balance into big int") + } + cost, err := act.Cost() + if err != nil { + return errors.Wrap(err, "failed to check cost of an action") + } + if balance.Cmp(cost) < 0 { + return errors.New("balance is not enough") + } + return nil +} diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go new file mode 100644 index 0000000000..8a6bc1eb7c --- /dev/null +++ b/ioctl/newcmd/action/action_test.go @@ -0,0 +1,127 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" +) + +func TestSigner(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(3) + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) + + t.Run("returns signer's address", func(t *testing.T) { + client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("test", nil).AnyTimes() + + cmd := NewActionCmd(client) + registerSignerFlag(client, cmd) + _, err := util.ExecuteCmd(cmd, "--signer", "test") + require.NoError(err) + result, err := Signer(client, cmd) + require.NoError(err) + require.Equal(result, "test") + }) +} + +func TestSendRaw(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + selp := &iotextypes.Action{} + + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(12) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(7) + + for _, test := range []struct { + endpoint string + insecure bool + }{ + { + endpoint: "111:222:333:444:5678", + insecure: false, + }, + { + endpoint: "", + insecure: true, + }, + } { + callbackEndpoint := func(cb func(*string, string, string, string)) { + cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage") + } + callbackInsecure := func(cb func(*bool, string, bool, string)) { + cb(&test.insecure, "insecure", !test.insecure, "insecure usage") + } + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint).Times(3) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure).Times(3) + + t.Run("sends raw action to blockchain", func(t *testing.T) { + response := &iotexapi.SendActionResponse{} + + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) + + cmd := NewActionCmd(client) + _, err := util.ExecuteCmd(cmd) + require.NoError(err) + + t.Run("endpoint iotexscan", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotexscan", + Endpoint: "testnet1", + }).Times(2) + + err = SendRaw(client, cmd, selp) + require.NoError(err) + }) + + t.Run("endpoint iotxplorer", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotxplorer", + }).Times(2) + + err := SendRaw(client, cmd, selp) + require.NoError(err) + }) + + t.Run("endpoint default", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "test", + }).Times(2) + + err := SendRaw(client, cmd, selp) + require.NoError(err) + }) + }) + } + + t.Run("failed to invoke SendAction api", func(t *testing.T) { + expectedErr := errors.New("failed to invoke SendAction api") + + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + + cmd := NewActionCmd(client) + _, err := util.ExecuteCmd(cmd) + require.NoError(err) + err = SendRaw(client, cmd, selp) + require.Contains(err.Error(), expectedErr.Error()) + }) +} From 8d1e8364a3be5835f53e407cbbe1ae80b321f821 Mon Sep 17 00:00:00 2001 From: Daulet Dulanov Date: Tue, 19 Jul 2022 02:55:25 +0200 Subject: [PATCH 6/6] [api] impl. TestGrpcServer_GetServerMeta (#3559) --- api/grpcserver_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/grpcserver_test.go b/api/grpcserver_test.go index 91b0c280f9..684304a456 100644 --- a/api/grpcserver_test.go +++ b/api/grpcserver_test.go @@ -104,7 +104,20 @@ func TestGrpcServer_GetReceiptByAction(t *testing.T) { } func TestGrpcServer_GetServerMeta(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + core := mock_apicoreservice.NewMockCoreService(ctrl) + grpcSvr := newGRPCHandler(core) + core.EXPECT().ServerMeta().Return("packageVersion", "packageCommitID", "gitStatus", "goVersion", "buildTime") + res, err := grpcSvr.GetServerMeta(context.Background(), &iotexapi.GetServerMetaRequest{}) + require.NoError(err) + require.Equal("packageVersion", res.ServerMeta.PackageVersion) + require.Equal("packageCommitID", res.ServerMeta.PackageCommitID) + require.Equal("gitStatus", res.ServerMeta.GitStatus) + require.Equal("goVersion", res.ServerMeta.GoVersion) + require.Equal("buildTime", res.ServerMeta.BuildTime) } func TestGrpcServer_ReadContract(t *testing.T) {