-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
4-nodes automated integration test infrastructure (#46)
* 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
1 parent
e0fe187
commit 0d74e61
Showing
4 changed files
with
351 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |