diff --git a/Gopkg.lock b/Gopkg.lock index 6c08a51d..587ffdd9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -48,7 +48,7 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:7f6e80c9a48014e57211353a9a82d12088f9dc85f564b6a70674bbc1d5abc34e" + digest = "1:485c3fd6d387a850672e86b89eabf32d0363890652c1b499898c925a5e8a02ce" name = "github.com/cosmos/cosmos-sdk" packages = [ "baseapp", @@ -66,21 +66,26 @@ "server", "server/config", "store", + "tests", "types", "version", "wire", "x/auth", "x/auth/client/cli", "x/auth/client/context", + "x/auth/client/rest", "x/bank", "x/bank/client", "x/bank/client/cli", + "x/bank/client/rest", "x/mock", "x/params", "x/slashing", "x/slashing/client/cli", + "x/slashing/client/rest", "x/stake", "x/stake/client/cli", + "x/stake/client/rest", "x/stake/keeper", "x/stake/tags", "x/stake/types", @@ -673,34 +678,50 @@ "github.com/cosmos/cosmos-sdk/client/rpc", "github.com/cosmos/cosmos-sdk/client/tx", "github.com/cosmos/cosmos-sdk/client/utils", + "github.com/cosmos/cosmos-sdk/crypto/keys", "github.com/cosmos/cosmos-sdk/server", "github.com/cosmos/cosmos-sdk/server/config", + "github.com/cosmos/cosmos-sdk/tests", "github.com/cosmos/cosmos-sdk/types", "github.com/cosmos/cosmos-sdk/version", "github.com/cosmos/cosmos-sdk/wire", "github.com/cosmos/cosmos-sdk/x/auth", "github.com/cosmos/cosmos-sdk/x/auth/client/cli", "github.com/cosmos/cosmos-sdk/x/auth/client/context", + "github.com/cosmos/cosmos-sdk/x/auth/client/rest", "github.com/cosmos/cosmos-sdk/x/bank", "github.com/cosmos/cosmos-sdk/x/bank/client/cli", + "github.com/cosmos/cosmos-sdk/x/bank/client/rest", "github.com/cosmos/cosmos-sdk/x/mock", "github.com/cosmos/cosmos-sdk/x/params", "github.com/cosmos/cosmos-sdk/x/slashing", "github.com/cosmos/cosmos-sdk/x/slashing/client/cli", + "github.com/cosmos/cosmos-sdk/x/slashing/client/rest", "github.com/cosmos/cosmos-sdk/x/stake", "github.com/cosmos/cosmos-sdk/x/stake/client/cli", + "github.com/cosmos/cosmos-sdk/x/stake/client/rest", + "github.com/gorilla/mux", "github.com/pkg/errors", "github.com/spf13/cobra", "github.com/spf13/pflag", "github.com/spf13/viper", "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", + "github.com/tendermint/go-amino", "github.com/tendermint/tendermint/abci/types", + "github.com/tendermint/tendermint/config", "github.com/tendermint/tendermint/crypto", "github.com/tendermint/tendermint/crypto/ed25519", "github.com/tendermint/tendermint/libs/cli", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", + "github.com/tendermint/tendermint/node", + "github.com/tendermint/tendermint/p2p", + "github.com/tendermint/tendermint/privval", + "github.com/tendermint/tendermint/proxy", + "github.com/tendermint/tendermint/rpc/core/types", + "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", ] solver-name = "gps-cdcl" diff --git a/cmd/kvcli/main.go b/cmd/kvcli/main.go index 75c9998c..1e3ad2a4 100644 --- a/cmd/kvcli/main.go +++ b/cmd/kvcli/main.go @@ -22,7 +22,7 @@ import ( paychancmd "github.com/kava-labs/kava/internal/x/paychan/client/cli" "github.com/kava-labs/kava/internal/app" - //"github.com/kava-labs/kava/internal/lcd" + "github.com/kava-labs/kava/internal/lcd" ) var ( @@ -59,8 +59,7 @@ func main() { advancedCmd.AddCommand( tendermintCmd, - //ibcCmd, - //lcd.ServeCommand(cdc), + lcd.ServeCommand(cdc), ) rootCmd.AddCommand( advancedCmd, diff --git a/internal/lcd/decode.go b/internal/lcd/decode.go index 59bb5f9a..a932e611 100644 --- a/internal/lcd/decode.go +++ b/internal/lcd/decode.go @@ -1,27 +1,24 @@ package lcd -/* import ( - "net/http" - //"encoding/hex" "encoding/base64" "encoding/json" + "net/http" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" ) -*/ -type TxBody struct { +type txBody struct { TxBase64 string `json:"txbase64"` } // Decode a tx from base64 into json -func DecodeTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { +func DecodeTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // get the input base64 string - var m TxBody + var m txBody decoder := json.NewDecoder(r.Body) err := decoder.Decode(&m) if err != nil { diff --git a/internal/lcd/lcd_test.go b/internal/lcd/lcd_test.go new file mode 100644 index 00000000..28f62789 --- /dev/null +++ b/internal/lcd/lcd_test.go @@ -0,0 +1,695 @@ +// Copyright 2016 All in Bits, inc +// Modifications copyright 2018 Kava Labs +package lcd + +import ( + "encoding/hex" + "fmt" + "net/http" + "regexp" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/client/tx" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + p2p "github.com/tendermint/tendermint/p2p" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + + client "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + rpc "github.com/cosmos/cosmos-sdk/client/rpc" + tests "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/client/rest" +) + +func init() { + cryptoKeys.BcryptSecurityParameter = 1 +} + +func TestKeys(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // get seed + // TODO Do we really need this endpoint? + res, body := Request(t, port, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + reg, err := regexp.Compile(`([a-z]+ ){12}`) + require.Nil(t, err) + match := reg.MatchString(seed) + require.True(t, match, "Returned seed has wrong format", seed) + + newName := "test_newname" + newPassword := "0987654321" + + // add key + jsonStr := []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed":"%s"}`, newName, newPassword, seed)) + res, body = Request(t, port, "POST", "/keys", jsonStr) + + require.Equal(t, http.StatusOK, res.StatusCode, body) + var resp keys.KeyOutput + err = wire.Cdc.UnmarshalJSON([]byte(body), &resp) + require.Nil(t, err, body) + + addr2Bech32 := resp.Address.String() + _, err = sdk.AccAddressFromBech32(addr2Bech32) + require.NoError(t, err, "Failed to return a correct bech32 address") + + // test if created account is the correct account + expectedInfo, _ := GetKeyBase(t).CreateKey(newName, seed, newPassword) + expectedAccount := sdk.AccAddress(expectedInfo.GetPubKey().Address().Bytes()) + assert.Equal(t, expectedAccount.String(), addr2Bech32) + + // existing keys + res, body = Request(t, port, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var m [2]keys.KeyOutput + err = cdc.UnmarshalJSON([]byte(body), &m) + require.Nil(t, err) + + addrBech32 := addr.String() + + require.Equal(t, name, m[0].Name, "Did not serve keys name correctly") + require.Equal(t, addrBech32, m[0].Address.String(), "Did not serve keys Address correctly") + require.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m[1].Address.String(), "Did not serve keys Address correctly") + + // select key + keyEndpoint := fmt.Sprintf("/keys/%s", newName) + res, body = Request(t, port, "GET", keyEndpoint, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var m2 keys.KeyOutput + err = cdc.UnmarshalJSON([]byte(body), &m2) + require.Nil(t, err) + + require.Equal(t, newName, m2.Name, "Did not serve keys name correctly") + require.Equal(t, addr2Bech32, m2.Address.String(), "Did not serve keys Address correctly") + + // update key + jsonStr = []byte(fmt.Sprintf(`{ + "old_password":"%s", + "new_password":"12345678901" + }`, newPassword)) + + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + // here it should say unauthorized as we changed the password before + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) + require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) + + // delete key + jsonStr = []byte(`{"password":"12345678901"}`) + res, body = Request(t, port, "DELETE", keyEndpoint, jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) +} + +func TestVersion(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + // node info + res, body := Request(t, port, "GET", "/version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match := reg.MatchString(body) + require.True(t, match, body) + + // node info + res, body = Request(t, port, "GET", "/node_version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err = regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match = reg.MatchString(body) + require.True(t, match, body) +} + +func TestNodeStatus(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + // node info + res, body := Request(t, port, "GET", "/node_info", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var nodeInfo p2p.NodeInfo + err := cdc.UnmarshalJSON([]byte(body), &nodeInfo) + require.Nil(t, err, "Couldn't parse node info") + + require.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) + + // syncing + res, body = Request(t, port, "GET", "/syncing", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + // we expect that there is no other node running so the syncing state is "false" + require.Equal(t, "false", body) +} + +func TestBlock(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + var resultBlock ctypes.ResultBlock + + res, body := Request(t, port, "GET", "/blocks/latest", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &resultBlock) + require.Nil(t, err, "Couldn't parse block") + + require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + + // -- + + res, body = Request(t, port, "GET", "/blocks/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = wire.Cdc.UnmarshalJSON([]byte(body), &resultBlock) + require.Nil(t, err, "Couldn't parse block") + + require.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + + // -- + + res, body = Request(t, port, "GET", "/blocks/1000000000", nil) + require.Equal(t, http.StatusNotFound, res.StatusCode, body) +} + +func TestValidators(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + var resultVals rpc.ResultValidatorsOutput + + res, body := Request(t, port, "GET", "/validatorsets/latest", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err := cdc.UnmarshalJSON([]byte(body), &resultVals) + require.Nil(t, err, "Couldn't parse validatorset") + + require.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + + require.Contains(t, resultVals.Validators[0].Address.String(), "cosmosvaladdr") + require.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalpub") + + // -- + + res, body = Request(t, port, "GET", "/validatorsets/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &resultVals) + require.Nil(t, err, "Couldn't parse validatorset") + + require.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + + // -- + + res, body = Request(t, port, "GET", "/validatorsets/1000000000", nil) + require.Equal(t, http.StatusNotFound, res.StatusCode, body) +} + +func TestCoinSend(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + require.NoError(t, err) + someFakeAddr := sdk.AccAddress(bz) + + // query empty + res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", someFakeAddr), nil) + require.Equal(t, http.StatusNoContent, res.StatusCode, body) + + acc := getAccount(t, port, addr) + initialBalance := acc.GetCoins() + + // create TX + receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc = getAccount(t, port, addr) + coins := acc.GetCoins() + mycoins := coins[0] + + require.Equal(t, "KVA", mycoins.Denom) + require.Equal(t, initialBalance[0].Amount.SubRaw(1), mycoins.Amount) + + // query receiver + acc = getAccount(t, port, receiveAddr) + coins = acc.GetCoins() + mycoins = coins[0] + + require.Equal(t, "KVA", mycoins.Denom) + require.Equal(t, int64(1), mycoins.Amount.Int64()) +} + +func TestTxs(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // query wrong + res, body := Request(t, port, "GET", "/txs", nil) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + + // query empty + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", "cosmosaccaddr1jawd35d9aq4u76sr3fjalmcqc8hqygs9gtnmv3"), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.Equal(t, "[]", body) + + // create TX + receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) + + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx is findable + res, body = Request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var indexedTxs []tx.Info + + // check if tx is queryable + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=tx.hash='%s'", resultTx.Hash), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NotEqual(t, "[]", body) + + err := cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + require.Equal(t, 1, len(indexedTxs)) + + // XXX should this move into some other testfile for txs in general? + // test if created TX hash is the correct hash + require.Equal(t, resultTx.Hash, indexedTxs[0].Hash) + + // query sender + // also tests url decoding + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32=%%27%s%%27", addr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + require.Equal(t, 1, len(indexedTxs), "%v", indexedTxs) // there are 2 txs created with doSend + require.Equal(t, resultTx.Height, indexedTxs[0].Height) + + // query recipient + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=recipient_bech32='%s'", receiveAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + require.Equal(t, 1, len(indexedTxs)) + require.Equal(t, resultTx.Height, indexedTxs[0].Height) +} + +func TestValidatorsQuery(t *testing.T) { + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + require.Equal(t, 1, len(pks)) + + validators := getValidators(t, port) + require.Equal(t, len(validators), 1) + + // make sure all the validators were found (order unknown because sorted by owner addr) + foundVal := false + pkBech := sdk.MustBech32ifyValPub(pks[0]) + if validators[0].PubKey == pkBech { + foundVal = true + } + require.True(t, foundVal, "pkBech %v, owner %v", pkBech, validators[0].Owner) +} + +func TestValidatorQuery(t *testing.T) { + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + require.Equal(t, 1, len(pks)) + + validator1Owner := sdk.AccAddress(pks[0].Address()) + + validator := getValidator(t, port, validator1Owner) + bech32ValAddress, err := sdk.Bech32ifyValPub(pks[0]) + require.NoError(t, err) + assert.Equal(t, validator.PubKey, bech32ValAddress, "The returned validator does not hold the correct data") +} + +func TestBonding(t *testing.T) { + name, password, denom := "test", "1234567890", "KVA" + addr, seed := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + validator1Owner := sdk.AccAddress(pks[0].Address()) + + // create bond TX + resultTx := doDelegate(t, port, seed, name, password, addr, validator1Owner) + tests.WaitForHeight(resultTx.Height+1, port) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + acc := getAccount(t, port, addr) + coins := acc.GetCoins() + + require.Equal(t, int64(40), coins.AmountOf(denom).Int64()) + + // query validator + bond := getDelegation(t, port, addr, validator1Owner) + require.Equal(t, "60.0000000000", bond.Shares) + + ////////////////////// + // testing unbonding + + // create unbond TX + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Owner) + tests.WaitForHeight(resultTx.Height+1, port) + + // query validator + bond = getDelegation(t, port, addr, validator1Owner) + require.Equal(t, "30.0000000000", bond.Shares) + + // check if tx was committed + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // should the sender should have not received any coins as the unbonding has only just begun + // query sender + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + require.Equal(t, int64(40), coins.AmountOf("KVA").Int64()) + + // query unbonding delegation + validatorAddr := sdk.AccAddress(pks[0].Address()) + unbondings := getUndelegations(t, port, addr, validatorAddr) + assert.Len(t, unbondings, 1, "Unbondings holds all unbonding-delegations") + assert.Equal(t, "30", unbondings[0].Balance.Amount.String()) + + // query summary + summary := getDelegationSummary(t, port, addr) + + assert.Len(t, summary.Delegations, 1, "Delegation summary holds all delegations") + assert.Equal(t, "30.0000000000", summary.Delegations[0].Shares) + assert.Len(t, summary.UnbondingDelegations, 1, "Delegation summary holds all unbonding-delegations") + assert.Equal(t, "30", summary.UnbondingDelegations[0].Balance.Amount.String()) + + // TODO add redelegation, need more complex capabilities such to mock context and + // TODO check summary for redelegation + // assert.Len(t, summary.Redelegations, 1, "Delegation summary holds all redelegations") + + // query txs + txs := getBondingTxs(t, port, addr, "") + assert.Len(t, txs, 2, "All Txs found") + + txs = getBondingTxs(t, port, addr, "bond") + assert.Len(t, txs, 1, "All bonding txs found") + + txs = getBondingTxs(t, port, addr, "unbond") + assert.Len(t, txs, 1, "All unbonding txs found") +} + +func TestUnrevoke(t *testing.T) { + _, password := "test", "1234567890" + addr, _ := CreateAddr(t, "test", password, GetKeyBase(t)) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + // XXX: any less than this and it fails + tests.WaitForHeight(3, port) + pkString, _ := sdk.Bech32ifyValPub(pks[0]) + signingInfo := getSigningInfo(t, port, pkString) + tests.WaitForHeight(4, port) + require.Equal(t, true, signingInfo.IndexOffset > 0) + require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil) + require.Equal(t, true, signingInfo.SignedBlocksCounter > 0) +} + +//_____________________________________________________________________________ +// get the account to get the sequence +func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { + res, body := Request(t, port, "GET", fmt.Sprintf("/accounts/%s", addr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var acc auth.Account + err := cdc.UnmarshalJSON([]byte(body), &acc) + require.Nil(t, err) + return acc +} + +func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { + + // create receive address + kb := client.MockKeyBase() + receiveInfo, _, err := kb.CreateMnemonic("receive_address", cryptoKeys.English, "1234567890", cryptoKeys.SigningAlgo("secp256k1")) + require.Nil(t, err) + receiveAddr = sdk.AccAddress(receiveInfo.GetPubKey().Address()) + + acc := getAccount(t, port, addr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + // send + coinbz, err := cdc.MarshalJSON(sdk.NewInt64Coin("KVA", 1)) + if err != nil { + panic(err) + } + + jsonStr := []byte(fmt.Sprintf(`{ + "name":"%s", + "password":"%s", + "account_number":"%d", + "sequence":"%d", + "gas": "10000", + "amount":[%s], + "chain_id":"%s" + }`, name, password, accnum, sequence, coinbz, chainID)) + res, body := Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &resultTx) + require.Nil(t, err) + + return receiveAddr, resultTx +} + +func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.ValidatorSigningInfo { + res, body := Request(t, port, "GET", fmt.Sprintf("/slashing/signing_info/%s", validatorPubKey), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var signingInfo slashing.ValidatorSigningInfo + err := cdc.UnmarshalJSON([]byte(body), &signingInfo) + require.Nil(t, err) + return signingInfo +} + +// ============= Stake Module ================ + +func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.AccAddress) rest.DelegationWithoutRat { + + // get the account to get the sequence + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/delegations/%s", delegatorAddr, validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var bond rest.DelegationWithoutRat + err := cdc.UnmarshalJSON([]byte(body), &bond) + require.Nil(t, err) + return bond +} + +func getUndelegations(t *testing.T, port string, delegatorAddr, validatorAddr sdk.AccAddress) []stake.UnbondingDelegation { + + // get the account to get the sequence + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/unbonding_delegations/%s", delegatorAddr, validatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var unbondings []stake.UnbondingDelegation + err := cdc.UnmarshalJSON([]byte(body), &unbondings) + require.Nil(t, err) + return unbondings +} + +func getDelegationSummary(t *testing.T, port string, delegatorAddr sdk.AccAddress) rest.DelegationSummary { + + // get the account to get the sequence + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s", delegatorAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var summary rest.DelegationSummary + err := cdc.UnmarshalJSON([]byte(body), &summary) + require.Nil(t, err) + return summary +} + +func getBondingTxs(t *testing.T, port string, delegatorAddr sdk.AccAddress, query string) []tx.Info { + + // get the account to get the sequence + var res *http.Response + var body string + if len(query) > 0 { + res, body = Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/txs?type=%s", delegatorAddr, query), nil) + } else { + res, body = Request(t, port, "GET", fmt.Sprintf("/stake/delegators/%s/txs", delegatorAddr), nil) + } + require.Equal(t, http.StatusOK, res.StatusCode, body) + var txs []tx.Info + err := cdc.UnmarshalJSON([]byte(body), &txs) + require.Nil(t, err) + return txs +} + +func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": "%d", + "sequence": "%d", + "gas": "10000", + "chain_id": "%s", + "delegations": [ + { + "delegator_addr": "%s", + "validator_addr": "%s", + "delegation": { "denom": "%s", "amount": "60" } + } + ], + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorAddr, "KVA")) + res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delegatorAddr), jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doBeginUnbonding(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": "%d", + "sequence": "%d", + "gas": "20000", + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [ + { + "delegator_addr": "%s", + "validator_addr": "%s", + "shares": "30" + } + ], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorAddr)) + res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delegatorAddr), jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doBeginRedelegation(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress) (resultTx ctypes.ResultBroadcastTxCommit) { + + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": "%d", + "sequence": "%d", + "gas": "10000", + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [ + { + "delegator_addr": "%s", + "validator_src_addr": "%s", + "validator_dst_addr": "%s", + "shares": "30" + } + ], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddr, validatorSrcAddr, validatorDstAddr)) + res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delegatorAddr), jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func getValidators(t *testing.T, port string) []stake.BechValidator { + // get the account to get the sequence + res, body := Request(t, port, "GET", "/stake/validators", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var validators []stake.BechValidator + err := cdc.UnmarshalJSON([]byte(body), &validators) + require.Nil(t, err) + return validators +} + +func getValidator(t *testing.T, port string, validatorAddr sdk.AccAddress) stake.BechValidator { + // get the account to get the sequence + res, body := Request(t, port, "GET", fmt.Sprintf("/stake/validators/%s", validatorAddr.String()), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + var validator stake.BechValidator + err := cdc.UnmarshalJSON([]byte(body), &validator) + require.Nil(t, err) + return validator +} diff --git a/internal/lcd/root.go b/internal/lcd/root.go index d879f4f0..989bd6d0 100644 --- a/internal/lcd/root.go +++ b/internal/lcd/root.go @@ -3,32 +3,27 @@ package lcd -/* import ( "net/http" "os" - "github.com/gorilla/mux" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tmlibs/log" - - tmserver "github.com/tendermint/tendermint/rpc/lib/server" - cmn "github.com/tendermint/tmlibs/common" - client "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" keys "github.com/cosmos/cosmos-sdk/client/keys" rpc "github.com/cosmos/cosmos-sdk/client/rpc" tx "github.com/cosmos/cosmos-sdk/client/tx" - version "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" - //ibc "github.com/cosmos/cosmos-sdk/x/ibc/client/rest" - //stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" + stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" + "github.com/gorilla/mux" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) -*/ // ServeCommand will generate a long-running rest server // (aka Light Client Daemon) that exposes functionality similar @@ -36,6 +31,7 @@ import ( func ServeCommand(cdc *wire.Codec) *cobra.Command { flagListenAddr := "laddr" flagCORS := "cors" + flagMaxOpenConnections := "max-open" cmd := &cobra.Command{ Use: "rest-server", @@ -43,48 +39,59 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { listenAddr := viper.GetString(flagListenAddr) handler := createHandler(cdc) - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). - With("module", "rest-server") - listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") + maxOpen := viper.GetInt(flagMaxOpenConnections) + + listener, err := tmserver.StartHTTPServer( + listenAddr, handler, logger, + tmserver.Config{MaxOpenConnections: maxOpen}, + ) if err != nil { return err } + logger.Info("REST server started") - // Wait forever and cleanup + // wait forever and cleanup cmn.TrapSignal(func() { err := listener.Close() - logger.Error("Error closing listener", "err", err) + logger.Error("error closing listener", "err", err) }) + return nil }, } - cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on") - cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)") - cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to") - cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") + + cmd.Flags().String(flagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") + cmd.Flags().String(flagCORS, "", "Set the domains that can make CORS requests (* for all)") + cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") + cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") + cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") + return cmd } func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() - r.HandleFunc("/version", version.RequestHandler).Methods("GET") kb, err := keys.GetKeyBase() //XXX if err != nil { panic(err) } - ctx := context.NewCoreContextFromViper() + cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout) + + r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") + r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") + r.HandleFunc("/decode_tx", DecodeTxRequestHandlerFn(cliCtx, cdc)).Methods("POST") - // TODO make more functional? aka r = keys.RegisterRoutes(r) keys.RegisterRoutes(r) - rpc.RegisterRoutes(ctx, r) - tx.RegisterRoutes(ctx, r, cdc) - auth.RegisterRoutes(ctx, r, cdc, "acc") - bank.RegisterRoutes(ctx, r, cdc, kb) - //ibc.RegisterRoutes(ctx, r, cdc, kb) - //stake.RegisterRoutes(ctx, r, cdc, kb) - r.HandleFunc("/decode_tx", DecodeTxRequestHandlerFn(ctx, cdc)).Methods("POST") + rpc.RegisterRoutes(cliCtx, r) + tx.RegisterRoutes(cliCtx, r, cdc) + auth.RegisterRoutes(cliCtx, r, cdc, "acc") + bank.RegisterRoutes(cliCtx, r, cdc, kb) + stake.RegisterRoutes(cliCtx, r, cdc, kb) + slashing.RegisterRoutes(cliCtx, r, cdc, kb) + return r } diff --git a/internal/lcd/test_helpers.go b/internal/lcd/test_helpers.go new file mode 100644 index 00000000..03a8f30f --- /dev/null +++ b/internal/lcd/test_helpers.go @@ -0,0 +1,275 @@ +// Copyright 2016 All in Bits, inc +// Modifications copyright 2018 Kava Labs + +package lcd + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + kapp "github.com/kava-labs/kava/internal/app" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + tmcfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/cli" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + nm "github.com/tendermint/tendermint/node" + pvm "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + tmrpc "github.com/tendermint/tendermint/rpc/lib/server" + tmtypes "github.com/tendermint/tendermint/types" +) + +// makePathname creates a unique pathname for each test. It will panic if it +// cannot get the current working directory. +func makePathname() string { + p, err := os.Getwd() + if err != nil { + panic(err) + } + + sep := string(filepath.Separator) + return strings.Replace(p, sep, "_", -1) +} + +// GetConfig returns a Tendermint config for the test cases. +func GetConfig() *tmcfg.Config { + pathname := makePathname() + config := tmcfg.ResetTestRoot(pathname) + + tmAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + rcpAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + grpcAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + config.P2P.ListenAddress = tmAddr + config.RPC.ListenAddress = rcpAddr + config.RPC.GRPCListenAddress = grpcAddr + + return config +} + +// GetKeyBase returns the LCD test keybase. It also requires that a directory +// could be made and a keybase could be fetched. +// +// NOTE: memDB cannot be used because the request is expecting to interact with +// the default location. +func GetKeyBase(t *testing.T) crkeys.Keybase { + dir, err := ioutil.TempDir("", "lcd_test") + require.NoError(t, err) + + viper.Set(cli.HomeFlag, dir) + + keybase, err := keys.GetKeyBase() + require.NoError(t, err) + + return keybase +} + +// CreateAddr adds an address to the key store and returns an address and seed. +// It also requires that the key could be created. +func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.AccAddress, string) { + var ( + err error + info crkeys.Info + seed string + ) + + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) + require.NoError(t, err) + + return sdk.AccAddress(info.GetPubKey().Address()), seed +} + +// InitializeTestLCD starts Tendermint and the LCD in process, listening on +// their respective sockets where nValidators is the total number of validators +// and initAddrs are the accounts to initialize with some KVA tokens. It +// returns a cleanup function, a set of validator public keys, and a port. +func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress) (func(), []crypto.PubKey, string) { + config := GetConfig() + config.Consensus.TimeoutCommit = 100 + config.Consensus.SkipTimeoutCommit = false + config.TxIndex.IndexAllTags = true + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + + privValidatorFile := config.PrivValidatorFile() + privVal := pvm.LoadOrGenFilePV(privValidatorFile) + privVal.Reset() + + db := dbm.NewMemDB() + app := kapp.NewKavaApp(logger, db, nil) + cdc = kapp.CreateKavaAppCodec() + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + require.NoError(t, err) + + if nValidators < 1 { + panic("InitializeTestLCD must use at least one validator") + } + + for i := 1; i < nValidators; i++ { + genDoc.Validators = append(genDoc.Validators, + tmtypes.GenesisValidator{ + PubKey: ed25519.GenPrivKey().PubKey(), + Power: 1, + Name: "val", + }, + ) + } + + var validatorsPKs []crypto.PubKey + + // NOTE: It's bad practice to reuse public key address for the owner + // address but doing in the test for simplicity. + var appGenTxs []json.RawMessage + for _, gdValidator := range genDoc.Validators { + pk := gdValidator.PubKey + validatorsPKs = append(validatorsPKs, pk) + + appGenTx, _, _, err := kapp.KavaAppGenTxNF(cdc, pk, sdk.AccAddress(pk.Address()), "test_val1") + require.NoError(t, err) + + appGenTxs = append(appGenTxs, appGenTx) + } + + genesisState, err := kapp.KavaAppGenState(cdc, appGenTxs[:]) + require.NoError(t, err) + + // add some tokens to init accounts + for _, addr := range initAddrs { + accAuth := auth.NewBaseAccountWithAddress(addr) + accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("KVA", 100)} + acc := kapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) + genesisState.StakeData.Pool.LooseTokens = genesisState.StakeData.Pool.LooseTokens.Add(sdk.NewRat(100)) + } + + appState, err := wire.MarshalJSONIndent(cdc, genesisState) + require.NoError(t, err) + genDoc.AppState = appState + + listenAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + + // XXX: Need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNode, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + + node, err := startTM(config, logger, genDoc, privVal, app) + require.NoError(t, err) + + lcd, err := startLCD(logger, listenAddr, cdc) + require.NoError(t, err) + + tests.WaitForLCDStart(port) + tests.WaitForHeight(1, port) + + cleanup := func() { + logger.Debug("cleaning up LCD initialization") + node.Stop() + node.Wait() + lcd.Close() + } + + return cleanup, validatorsPKs, port +} + +// startTM creates and starts an in-process Tendermint node with memDB and +// in-process ABCI application. It returns the new node or any error that +// occurred. +// +// TODO: Clean up the WAL dir or enable it to be not persistent! +func startTM( + tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, + privVal tmtypes.PrivValidator, app abci.Application, +) (*nm.Node, error) { + genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } + dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } + node, err := nm.NewNode( + tmcfg, + privVal, + proxy.NewLocalClientCreator(app), + genDocProvider, + dbProvider, + nm.DefaultMetricsProvider(tmcfg.Instrumentation), + logger.With("module", "node"), + ) + if err != nil { + return nil, err + } + + err = node.Start() + if err != nil { + return nil, err + } + + tests.WaitForRPC(tmcfg.RPC.ListenAddress) + logger.Info("Tendermint running!") + + return node, err +} + +// startLCD starts the LCD. +// +// NOTE: This causes the thread to block. +func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { + return tmrpc.StartHTTPServer(listenAddr, createHandler(cdc), logger, tmrpc.Config{}) +} + +// Request makes a test LCD test request. It returns a response object and a +// stringified response body. +func Request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { + var ( + err error + res *http.Response + ) + url := fmt.Sprintf("http://localhost:%v%v", port, path) + fmt.Println("REQUEST " + method + " " + url) + + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) + require.Nil(t, err) + + res, err = http.DefaultClient.Do(req) + require.Nil(t, err) + + output, err := ioutil.ReadAll(res.Body) + res.Body.Close() + require.Nil(t, err) + + return res, string(output) +} diff --git a/internal/lcd/version.go b/internal/lcd/version.go new file mode 100644 index 00000000..d385730d --- /dev/null +++ b/internal/lcd/version.go @@ -0,0 +1,32 @@ +// Copyright 2016 All in Bits, inc +// Modifications copyright 2018 Kava Labs + +package lcd + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/version" +) + +// cli version REST handler endpoint +func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) { + v := version.GetVersion() + w.Write([]byte(v)) +} + +// connected node version REST handler endpoint +func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + version, err := cliCtx.Query("/app/version") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + return + } + + w.Write(version) + } +} diff --git a/internal/lcd/wire.go b/internal/lcd/wire.go index c5d4c7cf..46295b47 100644 --- a/internal/lcd/wire.go +++ b/internal/lcd/wire.go @@ -1,11 +1,12 @@ +// Copyright 2016 All in Bits, inc +// Modifications copyright 2018 Kava Labs + package lcd -/* import ( amino "github.com/tendermint/go-amino" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -*/ var cdc = amino.NewCodec()