|
|
- package rpc
-
- import (
- "encoding/json"
- "fmt"
- "github.com/gorilla/websocket"
- "github.com/tendermint/tendermint/account"
- "github.com/tendermint/tendermint/binary"
- "github.com/tendermint/tendermint/rpc"
- "github.com/tendermint/tendermint/types"
- "net/http"
- "testing"
- "time"
- )
-
- //--------------------------------------------------------------------------------
- // Utilities for testing the websocket service
-
- // create a new connection
- func newWSCon(t *testing.T) *websocket.Conn {
- dialer := websocket.DefaultDialer
- rHeader := http.Header{}
- con, r, err := dialer.Dial(websocketAddr, rHeader)
- fmt.Println("response", r)
- if err != nil {
- t.Fatal(err)
- }
- return con
- }
-
- // subscribe to an event
- func subscribe(t *testing.T, con *websocket.Conn, eventid string) {
- err := con.WriteJSON(rpc.WSRequest{
- Type: "subscribe",
- Event: eventid,
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // unsubscribe from an event
- func unsubscribe(t *testing.T, con *websocket.Conn, eventid string) {
- err := con.WriteJSON(rpc.WSRequest{
- Type: "unsubscribe",
- Event: eventid,
- })
- if err != nil {
- t.Fatal(err)
- }
- }
-
- // wait for an event; do things that might trigger events, and check them when they are received
- func waitForEvent(t *testing.T, con *websocket.Conn, eventid string, dieOnTimeout bool, f func(), check func(string, []byte) error) {
- // go routine to wait for webscoket msg
- gch := make(chan []byte) // good channel
- ech := make(chan error) // error channel
- go func() {
- for {
- _, p, err := con.ReadMessage()
- if err != nil {
- ech <- err
- break
- } else {
- // if the event id isnt what we're waiting on
- // ignore it
- var response struct {
- Event string `json:"event"`
- }
- if err := json.Unmarshal(p, &response); err != nil {
- ech <- err
- break
- }
- if response.Event == eventid {
- gch <- p
- break
- }
- }
- }
- }()
-
- // do stuff (transactions)
- f()
-
- // wait for an event or 10 seconds
- ticker := time.Tick(10 * time.Second)
- select {
- case <-ticker:
- if dieOnTimeout {
- con.Close()
- t.Fatalf("%s event was not received in time", eventid)
- }
- // else that's great, we didn't hear the event
- // and we shouldn't have
- case p := <-gch:
- if dieOnTimeout {
- // message was received and expected
- // run the check
- err := check(eventid, p)
- if err != nil {
- t.Fatal(err)
- }
- } else {
- con.Close()
- t.Fatalf("%s event was not expected", eventid)
- }
- case err := <-ech:
- t.Fatal(err)
- }
- }
-
- func unmarshalResponseNewBlock(b []byte) (*types.Block, error) {
- // unmarshall and assert somethings
- var response struct {
- Event string `json:"event"`
- Data *types.Block `json:"data"`
- Error string `json:"error"`
- }
- var err error
- binary.ReadJSON(&response, b, &err)
- if err != nil {
- return nil, err
- }
- if response.Error != "" {
- return nil, fmt.Errorf(response.Error)
- }
- block := response.Data
- return block, nil
- }
-
- //--------------------------------------------------------------------------------
- // Test the websocket service
-
- // make a simple connection to the server
- func TestWSConnect(t *testing.T) {
- con := newWSCon(t)
- con.Close()
- }
-
- // receive a new block message
- func _TestWSNewBlock(t *testing.T) {
- con := newWSCon(t)
- eid := types.EventStringNewBlock()
- subscribe(t, con, eid)
- defer func() {
- unsubscribe(t, con, eid)
- con.Close()
- }()
- waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error {
- fmt.Println("Check:", string(b))
- return nil
- })
- }
-
- // receive a few new block messages in a row, with increasing height
- func TestWSBlockchainGrowth(t *testing.T) {
- con := newWSCon(t)
- eid := types.EventStringNewBlock()
- subscribe(t, con, eid)
- defer func() {
- unsubscribe(t, con, eid)
- con.Close()
- }()
- // listen for NewBlock, ensure height increases by 1
- unmarshalValidateBlockchain(t, con, eid)
- }
-
- // send a transaction and validate the events from listening for both sender and receiver
- func TestWSSend(t *testing.T) {
- toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
- amt := uint64(100)
-
- con := newWSCon(t)
- eidInput := types.EventStringAccInput(userByteAddr)
- eidOutput := types.EventStringAccOutput(toAddr)
- subscribe(t, con, eidInput)
- subscribe(t, con, eidOutput)
- defer func() {
- unsubscribe(t, con, eidInput)
- unsubscribe(t, con, eidOutput)
- con.Close()
- }()
- waitForEvent(t, con, eidInput, true, func() {
- broadcastTx(t, "JSONRPC", userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0)
- }, unmarshalValidateSend(amt, toAddr))
- waitForEvent(t, con, eidOutput, true, func() {}, unmarshalValidateSend(amt, toAddr))
- }
-
- // ensure events are only fired once for a given transaction
- func TestWSDoubleFire(t *testing.T) {
- con := newWSCon(t)
- eid := types.EventStringAccInput(userByteAddr)
- subscribe(t, con, eid)
- defer func() {
- unsubscribe(t, con, eid)
- con.Close()
- }()
- amt := uint64(100)
- toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
- // broadcast the transaction, wait to hear about it
- waitForEvent(t, con, eid, true, func() {
- broadcastTx(t, "JSONRPC", userByteAddr, toAddr, nil, userBytePriv, amt, 0, 0)
- }, func(eid string, b []byte) error {
- return nil
- })
- // but make sure we don't hear about it twice
- waitForEvent(t, con, eid, false, func() {
- }, func(eid string, b []byte) error {
- return nil
- })
- }
-
- // create a contract, wait for the event, and send it a msg, validate the return
- func TestWSCallWait(t *testing.T) {
- con := newWSCon(t)
- eid1 := types.EventStringAccInput(userByteAddr)
- subscribe(t, con, eid1)
- defer func() {
- unsubscribe(t, con, eid1)
- con.Close()
- }()
- amt := uint64(10000)
- code, returnCode, returnVal := simpleContract()
- var contractAddr []byte
- // wait for the contract to be created
- waitForEvent(t, con, eid1, true, func() {
- _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000)
- contractAddr = receipt.ContractAddr
- }, unmarshalValidateCall(amt, returnCode))
-
- // susbscribe to the new contract
- amt = uint64(10001)
- eid2 := types.EventStringAccReceive(contractAddr)
- subscribe(t, con, eid2)
- defer func() {
- unsubscribe(t, con, eid2)
- }()
- // get the return value from a call
- data := []byte{0x1} // just needs to be non empty for this to be a CallTx
- waitForEvent(t, con, eid2, true, func() {
- broadcastTx(t, "JSONRPC", userByteAddr, contractAddr, data, userBytePriv, amt, 1000, 1000)
- }, unmarshalValidateCall(amt, returnVal))
- }
-
- // create a contract and send it a msg without waiting. wait for contract event
- // and validate return
- func TestWSCallNoWait(t *testing.T) {
- con := newWSCon(t)
- amt := uint64(10000)
- code, _, returnVal := simpleContract()
-
- _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000)
- contractAddr := receipt.ContractAddr
-
- // susbscribe to the new contract
- amt = uint64(10001)
- eid := types.EventStringAccReceive(contractAddr)
- subscribe(t, con, eid)
- defer func() {
- unsubscribe(t, con, eid)
- con.Close()
- }()
- // get the return value from a call
- data := []byte{0x1} // just needs to be non empty for this to be a CallTx
- waitForEvent(t, con, eid, true, func() {
- broadcastTx(t, "JSONRPC", userByteAddr, contractAddr, data, userBytePriv, amt, 1000, 1000)
- }, unmarshalValidateCall(amt, returnVal))
- }
-
- // create two contracts, one of which calls the other
- func TestWSCallCall(t *testing.T) {
- con := newWSCon(t)
- amt := uint64(10000)
- code, _, returnVal := simpleContract()
- txid := new([]byte)
-
- // deploy the two contracts
- _, receipt := broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000)
- contractAddr1 := receipt.ContractAddr
- code, _, _ = simpleCallContract(contractAddr1)
- _, receipt = broadcastTx(t, "JSONRPC", userByteAddr, nil, code, userBytePriv, amt, 1000, 1000)
- contractAddr2 := receipt.ContractAddr
-
- // susbscribe to the new contracts
- amt = uint64(10001)
- eid1 := types.EventStringAccReceive(contractAddr1)
- eid2 := types.EventStringAccReceive(contractAddr2)
- subscribe(t, con, eid1)
- subscribe(t, con, eid2)
- defer func() {
- unsubscribe(t, con, eid1)
- unsubscribe(t, con, eid2)
- con.Close()
- }()
- // call contract2, which should call contract1, and wait for ev1
- data := []byte{0x1} // just needs to be non empty for this to be a CallTx
- waitForEvent(t, con, eid1, true, func() {
- tx, _ := broadcastTx(t, "JSONRPC", userByteAddr, contractAddr2, data, userBytePriv, amt, 1000, 1000)
- *txid = account.HashSignBytes(tx)
- }, unmarshalValidateCallCall(userByteAddr, returnVal, txid))
- }
|