Skip to content

Commit

Permalink
4-nodes automated integration test infrastructure (#46)
Browse files Browse the repository at this point in the history
* feat: added working Dockerfile

* test: multinode docker & docker-compose

* misc: added integration test in github action

* misc: divided unit test & integration test

* fix: added ETH port

* test: added basis lib

* misc: added test trigger

* test: added a test - delegation

* test: added test trigger

* test: added tests: store code

* test: added test - InstantiateContract

* test: added test - contract execution

* docs: added README of integration test

* misc: added go setup before integration test

* test: automated all prepared genesis.json creation process

resolves #48

* test: changed broadcast mode as block and delete sleep
  • Loading branch information
psy2848048 committed Mar 23, 2023
1 parent e0fe187 commit 0d74e61
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 1 deletion.
2 changes: 1 addition & 1 deletion integration_test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (t *WASMIntegrationTestSuite) Test01_SimpleDelegation() {
}

func (t *WASMIntegrationTestSuite) Test02_StoreCode() {
contractBytes, err := os.ReadFile(filepath.Join(".", "misc", "token.wasm"))
contractBytes, err := os.ReadFile(filepath.Join(".", "misc", "dezswap_token.wasm"))
if err != nil {
panic(err)
}
Expand Down
Binary file added integration_test/misc/dezswap_token.wasm
Binary file not shown.
94 changes: 94 additions & 0 deletions integration_test/rpcclient_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package integrationtest

import (
"bytes"
"errors"
"io"
"net/http"
"os"
"strings"
"time"
)

const (
CodeOK = 0

ErrStatusUnauthorized = "rest: unauthorized"
ErrUnknown = "rest: unknown error"

HeaderContentType = "Content-Type"
HeaderContentTypeJson = "application/json"

GET = "GET"
POST = "POST"
PUT = "PUT"
DELETE = "DELETE"

REST_TIMEOUT = 2 // in sec
)

type Method string

func RESTCallWithJson(nodeDomain string, endpoint string, method Method, param []byte) ([]byte, error) {
client := &http.Client{
Timeout: time.Duration(time.Second * REST_TIMEOUT),
}

for i := 0; i < 5; i++ {
url := strings.Join([]string{nodeDomain, endpoint}, "/")

req, err := http.NewRequest(string(method), url, bytes.NewReader(param))
if err != nil {
return []byte{}, err
}

req.Header.Set(HeaderContentType, HeaderContentTypeJson)

res, err := restCall(client, req)
if os.IsTimeout(err) {
continue
} else if err != nil {
return []byte{}, err
}

return res, nil
}

return nil, errors.New("exceeded trial")
}

func restCall(client *http.Client, req *http.Request) ([]byte, error) {
res, err := client.Do(req)
if err != nil {
return []byte{}, err
}

err = restErrorHandler(res)
if err != nil {
return []byte{}, err
}

defer func() {
if err := res.Body.Close(); err != nil {
logger.Printf("res.body.close, %s", err)
}
}()

resBody, err := io.ReadAll(res.Body)
if err != nil {
return []byte{}, err
}

return resBody, nil
}

func restErrorHandler(res *http.Response) error {
switch res.StatusCode {
case http.StatusOK:
return nil
case http.StatusUnauthorized:
return errors.New(ErrStatusUnauthorized)
default:
return errors.New(ErrUnknown)
}
}
256 changes: 256 additions & 0 deletions integration_test/wallet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package integrationtest

import (
"context"
"encoding/json"
"os"
"sync"

"github.com/pkg/errors"
"google.golang.org/grpc"

cosmwasmtype "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/cosmos/cosmos-sdk/client/tx"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simapp"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/cosmos/cosmos-sdk/types"
sdktype "github.com/cosmos/cosmos-sdk/types"
txtype "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
ethhd "github.com/evmos/ethermint/crypto/hd"
)

const (
Prefix = "xpla"
ChainID = "localtest_1-1"
)

type WalletInfo struct {
sync.Mutex

IsSrc bool
ChainId string
Prefix string
StringAddress string
ByteAddress sdktype.AccAddress
PrivKey cryptotypes.PrivKey
PubKey cryptotypes.PubKey
AccountNumber uint64
Sequence uint64
EncCfg simappparams.EncodingConfig
}

func NewWalletInfo(mnemonics string) (*WalletInfo, error) {
// derive key
fullFundraiserPath := "m/44'/60'/0'/0/0"

var privKey cryptotypes.PrivKey
var pubKey cryptotypes.PubKey
var byteAddress types.AccAddress
var stringAddress string

devFunc := ethhd.EthSecp256k1.Derive()
privBytes, err := devFunc(mnemonics, "", fullFundraiserPath)
if err != nil {
err = errors.Wrap(err, "NewWalletInfo, derive mnemonics -> rootkey")
return nil, err
}

genFunc := ethhd.EthSecp256k1.Generate()
privKey = genFunc(privBytes)
if err != nil {
err = errors.Wrap(err, "NewWalletInfo, rootkey -> privkey")
return nil, err
}
pubKey = privKey.PubKey()

byteAddress = types.AccAddress(pubKey.Address())
stringAddress, err = types.Bech32ifyAddressBytes(Prefix, pubKey.Address())
if err != nil {
err = errors.Wrap(err, "NewWalletInfo, create bech32 address from byte")
return nil, err
}

encCfg := simapp.MakeTestEncodingConfig()

accountNumber, seq, err := GetAccountNumber(desc.ServiceConn, ChainID, stringAddress)
if err != nil {
err = errors.Wrap(err, "NewWalletInfo, get account info")
return nil, err
}

ret := &WalletInfo{
ChainId: ChainID,
Prefix: Prefix,
ByteAddress: byteAddress,
StringAddress: stringAddress,
PrivKey: privKey,
PubKey: pubKey,
AccountNumber: accountNumber,
Sequence: seq,
EncCfg: encCfg,
}

return ret, nil
}

func (w *WalletInfo) SendTx(chainId string, msg types.Msg, fee types.Coin, gasLimit int64) (string, error) {
w.Lock()
defer w.Unlock()

txBuilder := w.EncCfg.TxConfig.NewTxBuilder()

err := txBuilder.SetMsgs(msg)
if err != nil {
err = errors.Wrap(err, "SendTx, set msgs")
return "", err
}

txBuilder.SetGasLimit(uint64(gasLimit))
txBuilder.SetFeeAmount(types.NewCoins(fee))
txBuilder.SetMemo("")

sigV2 := signing.SignatureV2{
PubKey: w.PrivKey.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: w.EncCfg.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: w.Sequence,
}
err = txBuilder.SetSignatures(sigV2)
if err != nil {
err = errors.Wrap(err, "SendTx, SetSignatures")
return "", err
}

signerData := xauthsigning.SignerData{
ChainID: chainId,
AccountNumber: w.AccountNumber,
Sequence: w.Sequence,
}

sigV2, err = tx.SignWithPrivKey(
w.EncCfg.TxConfig.SignModeHandler().DefaultMode(), signerData, txBuilder, w.PrivKey, w.EncCfg.TxConfig, w.Sequence)
if err != nil {
err = errors.Wrap(err, "SendTx, do sign")
return "", err
}

err = txBuilder.SetSignatures(sigV2)
if err != nil {
err = errors.Wrap(err, "SendTx, set signatures")
return "", err
}

txBytes, err := w.EncCfg.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
err = errors.Wrap(err, "SendTx, tx byte encode")
return "", err
}

txHash, err := BroadcastTx(desc.ServiceConn, w.ChainId, txBytes, txtype.BroadcastMode_BROADCAST_MODE_ASYNC)
if err != nil {
err = errors.Wrap(err, "SendTx, tx broadcast")
return "", err
}

w.Sequence += 1

return txHash, nil
}

func (w *WalletInfo) RefreshSequence() error {
w.Lock()
defer w.Unlock()

accountNumber, seq, err := GetAccountNumber(desc.ServiceConn, w.ChainId, w.StringAddress)
if err != nil {
err = errors.Wrap(err, "RefreshSequence, get account info")
return err
}

w.AccountNumber = accountNumber
w.Sequence = seq

return nil
}

func GRPCQueryContractStore(conn *grpc.ClientConn, chainId, contractAddress, param string) (*json.RawMessage, error) {
client := cosmwasmtype.NewQueryClient(desc.ServiceConn)
res, err := client.SmartContractState(context.Background(), &cosmwasmtype.QuerySmartContractStateRequest{
Address: contractAddress,
QueryData: []byte(param),
})

if err != nil {
err = errors.Wrap(err, "GRPCQueryContractStore")
return nil, err
}

resData := json.RawMessage(res.Data.Bytes())

return &resData, nil
}

func GenerateContractExecMessage(senderAddress, contractAddress string, param []byte, coins sdktype.Coins) *cosmwasmtype.MsgExecuteContract {
return &cosmwasmtype.MsgExecuteContract{
Sender: senderAddress,
Contract: contractAddress,
Msg: param,
Funds: coins,
}

}

func GetAccountNumber(conn *grpc.ClientConn, chainId, address string) (uint64, uint64, error) {
client := authtypes.NewQueryClient(desc.GetConnectionWithContext(context.Background()))

res, err := client.Account(context.Background(), &authtypes.QueryAccountRequest{Address: address})
if err != nil {
err = errors.Wrap(err, "GetAccountNumber")
return 0, 0, err
}

var baseAccount authtypes.ModuleAccount
err = baseAccount.Unmarshal(res.Account.Value)
if err != nil {
err = errors.Wrap(err, "GetAccountNumber, unmarshalling")
return 0, 0, err
}

return baseAccount.GetAccountNumber(), baseAccount.GetSequence(), nil
}

func BroadcastTx(conn *grpc.ClientConn, chainId string, txBytes []byte, mode txtype.BroadcastMode) (string, error) {
queryTxClient := txtype.NewServiceClient(desc.GetConnectionWithContext(context.Background()))

_, err := queryTxClient.Simulate(context.Background(), &txtype.SimulateRequest{
TxBytes: txBytes,
})

if err != nil {
return "", err
}

client := txtype.NewServiceClient(desc.ServiceConn)

if currtestingenv := os.Getenv("GOLANG_TESTING"); currtestingenv != "true" {
res, err := client.BroadcastTx(context.Background(), &txtype.BroadcastTxRequest{
TxBytes: txBytes,
Mode: mode,
})

if err != nil {
err = errors.Wrap(err, "broadcastTx")
return "", err
}

return res.TxResponse.TxHash, nil
}

return "", nil
}

0 comments on commit 0d74e61

Please sign in to comment.