From b52c874d12482fce093311d9bfcebf7fa56fde6b Mon Sep 17 00:00:00 2001
From: Ethan Buchman <ethan@coinculture.info>
Date: Thu, 28 Apr 2016 13:12:32 -0400
Subject: [PATCH] add rpc tests. not finished yet

---
 rpc/test/client_rpc_test.go | 129 ++++++++++++++
 rpc/test/client_ws_test.go  | 212 +++++++++++++++++++++++
 rpc/test/config.go          |  13 ++
 rpc/test/genesis.go         |  49 ++++++
 rpc/test/helpers.go         | 331 ++++++++++++++++++++++++++++++++++++
 rpc/test/tests.go           | 286 +++++++++++++++++++++++++++++++
 rpc/test/ws_helpers.go      | 278 ++++++++++++++++++++++++++++++
 7 files changed, 1298 insertions(+)
 create mode 100644 rpc/test/client_rpc_test.go
 create mode 100644 rpc/test/client_ws_test.go
 create mode 100644 rpc/test/config.go
 create mode 100644 rpc/test/genesis.go
 create mode 100644 rpc/test/helpers.go
 create mode 100644 rpc/test/tests.go
 create mode 100644 rpc/test/ws_helpers.go

diff --git a/rpc/test/client_rpc_test.go b/rpc/test/client_rpc_test.go
new file mode 100644
index 00000000..eb8a0945
--- /dev/null
+++ b/rpc/test/client_rpc_test.go
@@ -0,0 +1,129 @@
+package rpctest
+
+import (
+	"testing"
+
+	//	ctypes "github.com/eris-ltd/eris-db/rpc/core/types"
+	_ "github.com/tendermint/tendermint/config/tendermint_test"
+)
+
+// When run with `-test.short` we only run:
+// TestHTTPStatus, TestHTTPBroadcast, TestJSONStatus, TestJSONBroadcast, TestWSConnect, TestWSSend
+
+//--------------------------------------------------------------------------------
+func TestHTTPStatus(t *testing.T) {
+	testStatus(t, "HTTP")
+}
+
+func TestHTTPGenPriv(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGenPriv(t, "HTTP")
+}
+
+func TestHTTPGetAccount(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGetAccount(t, "HTTP")
+}
+
+func TestHTTPSignedTx(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testSignedTx(t, "HTTP")
+}
+
+func TestHTTPBroadcastTx(t *testing.T) {
+	testBroadcastTx(t, "HTTP")
+}
+
+func TestHTTPGetStorage(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGetStorage(t, "HTTP")
+}
+
+func TestHTTPCallCode(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testCallCode(t, "HTTP")
+}
+
+func TestHTTPCallContract(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testCall(t, "HTTP")
+}
+
+func TestHTTPNameReg(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testNameReg(t, "HTTP")
+}
+
+//--------------------------------------------------------------------------------
+// Test the JSONRPC client
+
+func TestJSONStatus(t *testing.T) {
+	testStatus(t, "JSONRPC")
+}
+
+func TestJSONGenPriv(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGenPriv(t, "JSONRPC")
+}
+
+func TestJSONGetAccount(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGetAccount(t, "JSONRPC")
+}
+
+func TestJSONSignedTx(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testSignedTx(t, "JSONRPC")
+}
+
+func TestJSONBroadcastTx(t *testing.T) {
+	testBroadcastTx(t, "JSONRPC")
+}
+
+func TestJSONGetStorage(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testGetStorage(t, "JSONRPC")
+}
+
+func TestJSONCallCode(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testCallCode(t, "JSONRPC")
+}
+
+func TestJSONCallContract(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testCall(t, "JSONRPC")
+}
+
+func TestJSONNameReg(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	testNameReg(t, "JSONRPC")
+}
diff --git a/rpc/test/client_ws_test.go b/rpc/test/client_ws_test.go
new file mode 100644
index 00000000..94e85508
--- /dev/null
+++ b/rpc/test/client_ws_test.go
@@ -0,0 +1,212 @@
+package rpctest
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/eris-ltd/eris-db/txs"
+	_ "github.com/tendermint/tendermint/config/tendermint_test"
+)
+
+var wsTyp = "JSONRPC"
+
+//--------------------------------------------------------------------------------
+// Test the websocket service
+
+// make a simple connection to the server
+func TestWSConnect(t *testing.T) {
+	wsc := newWSClient(t)
+	wsc.Stop()
+}
+
+// receive a new block message
+func TestWSNewBlock(t *testing.T) {
+	wsc := newWSClient(t)
+	eid := types.EventStringNewBlock()
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+	waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error {
+		fmt.Println("Check:", string(b.([]byte)))
+		return nil
+	})
+}
+
+// receive a few new block messages in a row, with increasing height
+func TestWSBlockchainGrowth(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	wsc := newWSClient(t)
+	eid := types.EventStringNewBlock()
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+	// listen for NewBlock, ensure height increases by 1
+	unmarshalValidateBlockchain(t, wsc, eid)
+}
+
+// send a transaction and validate the events from listening for both sender and receiver
+func TestWSSend(t *testing.T) {
+	toAddr := user[1].Address
+	amt := int64(100)
+
+	wsc := newWSClient(t)
+	eidInput := types.EventStringAccInput(user[0].Address)
+	eidOutput := types.EventStringAccOutput(toAddr)
+	subscribe(t, wsc, eidInput)
+	subscribe(t, wsc, eidOutput)
+	defer func() {
+		unsubscribe(t, wsc, eidInput)
+		unsubscribe(t, wsc, eidOutput)
+		wsc.Stop()
+	}()
+	waitForEvent(t, wsc, eidInput, true, func() {
+		tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt)
+		broadcastTx(t, wsTyp, tx)
+	}, unmarshalValidateSend(amt, toAddr))
+	waitForEvent(t, wsc, eidOutput, true, func() {}, unmarshalValidateSend(amt, toAddr))
+}
+
+// ensure events are only fired once for a given transaction
+func TestWSDoubleFire(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	wsc := newWSClient(t)
+	eid := types.EventStringAccInput(user[0].Address)
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+	amt := int64(100)
+	toAddr := user[1].Address
+	// broadcast the transaction, wait to hear about it
+	waitForEvent(t, wsc, eid, true, func() {
+		tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt)
+		broadcastTx(t, wsTyp, tx)
+	}, func(eid string, b interface{}) error {
+		return nil
+	})
+	// but make sure we don't hear about it twice
+	waitForEvent(t, wsc, eid, false, func() {
+	}, func(eid string, b interface{}) error {
+		return nil
+	})
+}
+
+// create a contract, wait for the event, and send it a msg, validate the return
+func TestWSCallWait(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	wsc := newWSClient(t)
+	eid1 := types.EventStringAccInput(user[0].Address)
+	subscribe(t, wsc, eid1)
+	defer func() {
+		unsubscribe(t, wsc, eid1)
+		wsc.Stop()
+	}()
+	amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
+	code, returnCode, returnVal := simpleContract()
+	var contractAddr []byte
+	// wait for the contract to be created
+	waitForEvent(t, wsc, eid1, true, func() {
+		tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
+		receipt := broadcastTx(t, wsTyp, tx)
+		contractAddr = receipt.ContractAddr
+	}, unmarshalValidateTx(amt, returnCode))
+
+	// susbscribe to the new contract
+	amt = int64(10001)
+	eid2 := types.EventStringAccOutput(contractAddr)
+	subscribe(t, wsc, eid2)
+	defer func() {
+		unsubscribe(t, wsc, eid2)
+	}()
+	// get the return value from a call
+	data := []byte{0x1}
+	waitForEvent(t, wsc, eid2, true, func() {
+		tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee)
+		receipt := broadcastTx(t, wsTyp, tx)
+		contractAddr = receipt.ContractAddr
+	}, unmarshalValidateTx(amt, returnVal))
+}
+
+// create a contract and send it a msg without waiting. wait for contract event
+// and validate return
+func TestWSCallNoWait(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	wsc := newWSClient(t)
+	amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
+	code, _, returnVal := simpleContract()
+
+	tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
+	receipt := broadcastTx(t, wsTyp, tx)
+	contractAddr := receipt.ContractAddr
+
+	// susbscribe to the new contract
+	amt = int64(10001)
+	eid := types.EventStringAccOutput(contractAddr)
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+	// get the return value from a call
+	data := []byte{0x1}
+	waitForEvent(t, wsc, eid, true, func() {
+		tx := makeDefaultCallTx(t, wsTyp, contractAddr, data, amt, gasLim, fee)
+		broadcastTx(t, wsTyp, tx)
+	}, unmarshalValidateTx(amt, returnVal))
+}
+
+// create two contracts, one of which calls the other
+func TestWSCallCall(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	wsc := newWSClient(t)
+	amt, gasLim, fee := int64(10000), int64(1000), int64(1000)
+	code, _, returnVal := simpleContract()
+	txid := new([]byte)
+
+	// deploy the two contracts
+	tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
+	receipt := broadcastTx(t, wsTyp, tx)
+	contractAddr1 := receipt.ContractAddr
+
+	code, _, _ = simpleCallContract(contractAddr1)
+	tx = makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee)
+	receipt = broadcastTx(t, wsTyp, tx)
+	contractAddr2 := receipt.ContractAddr
+
+	// susbscribe to the new contracts
+	amt = int64(10001)
+	eid1 := types.EventStringAccCall(contractAddr1)
+	subscribe(t, wsc, eid1)
+	defer func() {
+		unsubscribe(t, wsc, eid1)
+		wsc.Stop()
+	}()
+	// call contract2, which should call contract1, and wait for ev1
+
+	// let the contract get created first
+	waitForEvent(t, wsc, eid1, true, func() {
+	}, func(eid string, b interface{}) error {
+		return nil
+	})
+	// call it
+	waitForEvent(t, wsc, eid1, true, func() {
+		tx := makeDefaultCallTx(t, wsTyp, contractAddr2, nil, amt, gasLim, fee)
+		broadcastTx(t, wsTyp, tx)
+		*txid = types.TxID(chainID, tx)
+	}, unmarshalValidateCall(user[0].Address, returnVal, txid))
+}
diff --git a/rpc/test/config.go b/rpc/test/config.go
new file mode 100644
index 00000000..2fe20981
--- /dev/null
+++ b/rpc/test/config.go
@@ -0,0 +1,13 @@
+package rpctest
+
+import (
+	cfg "github.com/tendermint/go-config"
+)
+
+var config cfg.Config = nil
+
+func init() {
+	cfg.OnConfig(func(newConfig cfg.Config) {
+		config = newConfig
+	})
+}
diff --git a/rpc/test/genesis.go b/rpc/test/genesis.go
new file mode 100644
index 00000000..d1ee5621
--- /dev/null
+++ b/rpc/test/genesis.go
@@ -0,0 +1,49 @@
+package rpctest
+
+// priv keys generated deterministically eg rpc/tests/helpers.go
+var defaultGenesis = `{
+  "chain_id" : "tendermint_test",
+  "accounts": [
+    {
+	    "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB",
+	    "amount": 200000000
+    },
+    {
+	    "address": "DFE4AFFA4CEE17CD01CB9E061D77C3ECED29BD88",
+	    "amount": 200000000
+    },
+    {
+	    "address": "F60D30722E7B497FA532FB3207C3FB29C31B1992",
+	    "amount": 200000000
+    },
+    {
+	    "address": "336CB40A5EB92E496E19B74FDFF2BA017C877FD6",
+	    "amount": 200000000
+    },
+    {
+	    "address": "D218F0F439BF0384F6F5EF8D0F8B398D941BD1DC",
+	    "amount": 200000000
+    }
+  ],
+  "validators": [
+    {
+      "pub_key": [1, "583779C3BFA3F6C7E23C7D830A9C3D023A216B55079AD38BFED1207B94A19548"],
+      "amount": 1000000,
+      "unbond_to": [
+        {
+          "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB",
+          "amount":  100000
+        }
+      ]
+    }
+  ]
+}`
+
+var defaultPrivValidator = `{
+  "address": "1D7A91CB32F758A02EBB9BE1FB6F8DEE56F90D42",
+	"pub_key": [1,"06FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8"],
+	"priv_key": [1,"C453604BD6480D5538B4C6FD2E3E314B5BCE518D75ADE4DA3DA85AB8ADFD819606FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8"],
+	"last_height":0,
+	"last_round":0,
+	"last_step":0
+}`
diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go
new file mode 100644
index 00000000..91b9154d
--- /dev/null
+++ b/rpc/test/helpers.go
@@ -0,0 +1,331 @@
+package rpctest
+
+import (
+	"bytes"
+	"strconv"
+	"testing"
+
+	acm "github.com/eris-ltd/eris-db/account"
+	edb "github.com/eris-ltd/eris-db/erisdb"
+	edbcli "github.com/eris-ltd/eris-db/rpc/client"
+	ctypes "github.com/eris-ltd/eris-db/rpc/core/types"
+	sm "github.com/eris-ltd/eris-db/state"
+	stypes "github.com/eris-ltd/eris-db/state/types"
+	edbapp "github.com/eris-ltd/eris-db/tmsp"
+	txs "github.com/eris-ltd/eris-db/txs"
+
+	. "github.com/tendermint/go-common"
+	"github.com/tendermint/go-crypto"
+	dbm "github.com/tendermint/go-db"
+	"github.com/tendermint/go-events"
+	"github.com/tendermint/go-p2p"
+	rpcclient "github.com/tendermint/go-rpc/client"
+	"github.com/tendermint/go-wire"
+
+	"github.com/tendermint/tendermint/config/tendermint_test"
+	nm "github.com/tendermint/tendermint/node"
+	"github.com/tendermint/tendermint/types"
+)
+
+// global variables for use across all tests
+var (
+	node              *nm.Node
+	mempoolCount      = 0
+	chainID           string
+	rpcAddr           string
+	requestAddr       string
+	websocketAddr     string
+	websocketEndpoint string
+	clientURI         *rpcclient.ClientURI
+	clientJSON        *rpcclient.ClientJSONRPC
+
+	user    = makeUsers(5) // make keys
+	clients map[string]rpcclient.Client
+)
+
+func init() {
+	tendermint_test.ResetConfig("rpc_test_client_test")
+	initGlobalVariables()
+
+	saveNewPriv()
+}
+
+// initialize config and create new node
+func initGlobalVariables() {
+	chainID = config.GetString("tm.chain_id")
+	rpcAddr = config.GetString("tm.rpc_laddr")
+	config.Set("erisdb.chain_id", chainID)
+	requestAddr = rpcAddr
+	websocketAddr = rpcAddr
+	websocketEndpoint = "/websocket"
+
+	clientURI = rpcclient.NewClientURI(requestAddr)
+	clientJSON = rpcclient.NewClientJSONRPC(requestAddr)
+
+	clients = map[string]rpcclient.Client{
+		"JSONRPC": clientJSON,
+		"HTTP":    clientURI,
+	}
+
+	// write the genesis
+	MustWriteFile(config.GetString("tm.genesis_file"), []byte(defaultGenesis), 0600)
+
+	// TODO: change consensus/state.go timeouts to be shorter
+
+	// start a node
+	ready := make(chan struct{})
+	go newNode(ready)
+	<-ready
+}
+
+// deterministic account generation, synced with genesis file in config/tendermint_test/config.go
+func makeUsers(n int) []*acm.PrivAccount {
+	accounts := []*acm.PrivAccount{}
+	for i := 0; i < n; i++ {
+		secret := ("mysecret" + strconv.Itoa(i))
+		user := acm.GenPrivAccountFromSecret(secret)
+		accounts = append(accounts, user)
+	}
+	return accounts
+}
+
+// create a new node and sleep forever
+func newNode(ready chan struct{}) {
+	stateDB := dbm.GetDB("app_state", "memdb", "")
+	genDoc, state := sm.MakeGenesisStateFromFile(stateDB, config.GetString("tm.genesis_file"))
+	state.Save()
+	buf, n, err := new(bytes.Buffer), new(int), new(error)
+	wire.WriteJSON(genDoc, buf, n, err)
+	stateDB.Set(stypes.GenDocKey, buf.Bytes())
+	if *err != nil {
+		Exit(Fmt("Unable to write gendoc to db: %v", err))
+	}
+	evsw := events.NewEventSwitch()
+	evsw.Start()
+	// create the app
+	app := edbapp.NewErisDBApp(state, evsw)
+
+	// Create & start node
+	privValidatorFile := config.GetString("tm.priv_validator_file")
+	privValidator := types.LoadOrGenPrivValidator(privValidatorFile)
+	node = nm.NewNode(privValidator, nm.GetProxyApp)
+	l := p2p.NewDefaultListener("tcp", config.GetString("tm.node_laddr"), true)
+	node.AddListener(l)
+	node.Start()
+
+	// Run the RPC server.
+	edb.StartRPC(node, app)
+	ready <- struct{}{}
+
+	// Sleep forever
+	ch := make(chan struct{})
+	<-ch
+}
+
+func saveNewPriv() {
+	// Save new priv_validator file.
+	priv := &types.PrivValidator{
+		Address: user[0].Address,
+		PubKey:  crypto.PubKeyEd25519(user[0].PubKey.(crypto.PubKeyEd25519)),
+		PrivKey: crypto.PrivKeyEd25519(user[0].PrivKey.(crypto.PrivKeyEd25519)),
+	}
+	priv.SetFile(config.GetString("tm.priv_validator_file"))
+	priv.Save()
+}
+
+//-------------------------------------------------------------------------------
+// some default transaction functions
+
+func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt int64) *txs.SendTx {
+	nonce := getNonce(t, typ, user[0].Address)
+	tx := txs.NewSendTx()
+	tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1)
+	tx.AddOutput(addr, amt)
+	return tx
+}
+
+func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt int64) *txs.SendTx {
+	tx := makeDefaultSendTx(t, typ, addr, amt)
+	tx.SignInput(chainID, 0, user[0])
+	return tx
+}
+
+func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee int64) *txs.CallTx {
+	nonce := getNonce(t, typ, user[0].Address)
+	tx := txs.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce+1)
+	tx.Sign(chainID, user[0])
+	return tx
+}
+
+func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee int64) *txs.NameTx {
+	nonce := getNonce(t, typ, user[0].Address)
+	tx := txs.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1)
+	tx.Sign(chainID, user[0])
+	return tx
+}
+
+//-------------------------------------------------------------------------------
+// rpc call wrappers (fail on err)
+
+// get an account's nonce
+func getNonce(t *testing.T, typ string, addr []byte) int {
+	client := clients[typ]
+	ac, err := edbcli.GetAccount(client, addr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if ac == nil {
+		return 0
+	}
+	return ac.Sequence
+}
+
+// get the account
+func getAccount(t *testing.T, typ string, addr []byte) *acm.Account {
+	client := clients[typ]
+	ac, err := edbcli.GetAccount(client, addr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return ac
+}
+
+// sign transaction
+func signTx(t *testing.T, typ string, tx txs.Tx, privAcc *acm.PrivAccount) txs.Tx {
+	client := clients[typ]
+	signedTx, err := edbcli.SignTx(client, tx, []*acm.PrivAccount{privAcc})
+	if err != nil {
+		t.Fatal(err)
+	}
+	return signedTx
+}
+
+// broadcast transaction
+func broadcastTx(t *testing.T, typ string, tx txs.Tx) ctypes.Receipt {
+	client := clients[typ]
+	rec, err := edbcli.BroadcastTx(client, tx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	mempoolCount += 1
+	return rec
+}
+
+// dump all storage for an account. currently unused
+func dumpStorage(t *testing.T, addr []byte) *ctypes.ResultDumpStorage {
+	client := clients["HTTP"]
+	resp, err := edbcli.DumpStorage(client, addr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return resp
+}
+
+func getStorage(t *testing.T, typ string, addr, key []byte) []byte {
+	client := clients[typ]
+	resp, err := edbcli.GetStorage(client, addr, key)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return resp
+}
+
+func callCode(t *testing.T, client rpcclient.Client, fromAddress, code, data, expected []byte) {
+	resp, err := edbcli.CallCode(client, fromAddress, code, data)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ret := resp.Return
+	// NOTE: we don't flip memory when it comes out of RETURN (?!)
+	if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 {
+		t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected)
+	}
+}
+
+func callContract(t *testing.T, client rpcclient.Client, fromAddress, toAddress, data, expected []byte) {
+	resp, err := edbcli.Call(client, fromAddress, toAddress, data)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ret := resp.Return
+	// NOTE: we don't flip memory when it comes out of RETURN (?!)
+	if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 {
+		t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected)
+	}
+}
+
+// get the namereg entry
+func getNameRegEntry(t *testing.T, typ string, name string) *txs.NameRegEntry {
+	client := clients[typ]
+	entry, err := edbcli.GetName(client, name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return entry
+}
+
+//--------------------------------------------------------------------------------
+// utility verification function
+
+func checkTx(t *testing.T, fromAddr []byte, priv *acm.PrivAccount, tx *txs.SendTx) {
+	if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 {
+		t.Fatal("Tx input addresses don't match!")
+	}
+
+	signBytes := acm.SignBytes(chainID, tx)
+	in := tx.Inputs[0] //(*types.SendTx).Inputs[0]
+
+	if err := in.ValidateBasic(); err != nil {
+		t.Fatal(err)
+	}
+	// Check signatures
+	// acc := getAccount(t, byteAddr)
+	// NOTE: using the acc here instead of the in fails; it is nil.
+	if !in.PubKey.VerifyBytes(signBytes, in.Signature) {
+		t.Fatal(txs.ErrTxInvalidSignature)
+	}
+}
+
+// simple contract returns 5 + 6 = 0xb
+func simpleContract() ([]byte, []byte, []byte) {
+	// this is the code we want to run when the contract is called
+	contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
+	// the is the code we need to return the contractCode when the contract is initialized
+	lenCode := len(contractCode)
+	// push code to the stack
+	//code := append([]byte{byte(0x60 + lenCode - 1)}, RightPadWord256(contractCode).Bytes()...)
+	code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...)
+	// store it in memory
+	code = append(code, []byte{0x60, 0x0, 0x52}...)
+	// return whats in memory
+	//code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...)
+	code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
+	// return init code, contract code, expected return
+	return code, contractCode, LeftPadBytes([]byte{0xb}, 32)
+}
+
+// simple call contract calls another contract
+func simpleCallContract(addr []byte) ([]byte, []byte, []byte) {
+	gas1, gas2 := byte(0x1), byte(0x1)
+	value := byte(0x1)
+	inOff, inSize := byte(0x0), byte(0x0) // no call data
+	retOff, retSize := byte(0x0), byte(0x20)
+	// this is the code we want to run (call a contract and return)
+	contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73}
+	contractCode = append(contractCode, addr...)
+	contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...)
+
+	// the is the code we need to return; the contractCode when the contract is initialized
+	// it should copy the code from the input into memory
+	lenCode := len(contractCode)
+	memOff := byte(0x0)
+	inOff = byte(0xc) // length of code before codeContract
+	length := byte(lenCode)
+
+	code := []byte{0x60, length, 0x60, inOff, 0x60, memOff, 0x37}
+	// return whats in memory
+	code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...)
+	code = append(code, contractCode...)
+	// return init code, contract code, expected return
+	return code, contractCode, LeftPadBytes([]byte{0xb}, 32)
+}
diff --git a/rpc/test/tests.go b/rpc/test/tests.go
new file mode 100644
index 00000000..b6ed2c84
--- /dev/null
+++ b/rpc/test/tests.go
@@ -0,0 +1,286 @@
+package rpctest
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	edbcli "github.com/eris-ltd/eris-db/rpc/client"
+	"github.com/eris-ltd/eris-db/txs"
+
+	. "github.com/tendermint/go-common"
+)
+
+var doNothing = func(eid string, b interface{}) error { return nil }
+
+func testStatus(t *testing.T, typ string) {
+	client := clients[typ]
+	resp, err := edbcli.Status(client)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Println(resp)
+	if resp.NodeInfo.Network != chainID {
+		t.Fatal(fmt.Errorf("ChainID mismatch: got %s expected %s",
+			resp.NodeInfo.Network, chainID))
+	}
+}
+
+func testGenPriv(t *testing.T, typ string) {
+	client := clients[typ]
+	privAcc, err := edbcli.GenPrivAccount(client)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(privAcc.Address) == 0 {
+		t.Fatal("Failed to generate an address")
+	}
+}
+
+func testGetAccount(t *testing.T, typ string) {
+	acc := getAccount(t, typ, user[0].Address)
+	if acc == nil {
+		t.Fatalf("Account was nil")
+	}
+	if bytes.Compare(acc.Address, user[0].Address) != 0 {
+		t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, user[0].Address)
+	}
+}
+
+func testSignedTx(t *testing.T, typ string) {
+	amt := int64(100)
+	toAddr := user[1].Address
+	testOneSignTx(t, typ, toAddr, amt)
+
+	toAddr = user[2].Address
+	testOneSignTx(t, typ, toAddr, amt)
+
+	toAddr = user[3].Address
+	testOneSignTx(t, typ, toAddr, amt)
+}
+
+func testOneSignTx(t *testing.T, typ string, addr []byte, amt int64) {
+	tx := makeDefaultSendTx(t, typ, addr, amt)
+	tx2 := signTx(t, typ, tx, user[0])
+	tx2hash := types.TxID(chainID, tx2)
+	tx.SignInput(chainID, 0, user[0])
+	txhash := types.TxID(chainID, tx)
+	if bytes.Compare(txhash, tx2hash) != 0 {
+		t.Fatal("Got different signatures for signing via rpc vs tx_utils")
+	}
+
+	tx_ := signTx(t, typ, tx, user[0])
+	tx = tx_.(*types.SendTx)
+	checkTx(t, user[0].Address, user[0], tx)
+}
+
+func testBroadcastTx(t *testing.T, typ string) {
+	amt := int64(100)
+	toAddr := user[1].Address
+	tx := makeDefaultSendTxSigned(t, typ, toAddr, amt)
+	receipt := broadcastTx(t, typ, tx)
+	if receipt.CreatesContract > 0 {
+		t.Fatal("This tx does not create a contract")
+	}
+	if len(receipt.TxHash) == 0 {
+		t.Fatal("Failed to compute tx hash")
+	}
+	pool := node.MempoolReactor().Mempool
+	txs := pool.Reap(-1)
+	if len(txs) != mempoolCount {
+		t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount)
+	}
+	tx2 := types.DecodeTx(txs[mempoolCount-1]).(*types.SendTx)
+	n, err := new(int), new(error)
+	buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer)
+	tx.WriteSignBytes(chainID, buf1, n, err)
+	tx2.WriteSignBytes(chainID, buf2, n, err)
+	if bytes.Compare(buf1.Bytes(), buf2.Bytes()) != 0 {
+		t.Fatal("inconsistent hashes for mempool tx and sent tx")
+	}
+}
+
+func testGetStorage(t *testing.T, typ string) {
+	wsc := newWSClient(t)
+	eid := types.EventStringNewBlock()
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+
+	amt, gasLim, fee := int64(1100), int64(1000), int64(1000)
+	code := []byte{0x60, 0x5, 0x60, 0x1, 0x55}
+	tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee)
+	receipt := broadcastTx(t, typ, tx)
+	if receipt.CreatesContract == 0 {
+		t.Fatal("This tx creates a contract")
+	}
+	if len(receipt.TxHash) == 0 {
+		t.Fatal("Failed to compute tx hash")
+	}
+	contractAddr := receipt.ContractAddr
+	if len(contractAddr) == 0 {
+		t.Fatal("Creates contract but resulting address is empty")
+	}
+
+	// allow it to get mined
+	waitForEvent(t, wsc, eid, true, func() {}, doNothing)
+	mempoolCount = 0
+
+	v := getStorage(t, typ, contractAddr, []byte{0x1})
+	got := LeftPadWord256(v)
+	expected := LeftPadWord256([]byte{0x5})
+	if got.Compare(expected) != 0 {
+		t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes())
+	}
+}
+
+func testCallCode(t *testing.T, typ string) {
+	client := clients[typ]
+
+	// add two integers and return the result
+	code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
+	data := []byte{}
+	expected := []byte{0xb}
+	callCode(t, client, user[0].PubKey.Address(), code, data, expected)
+
+	// pass two ints as calldata, add, and return the result
+	code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3}
+	data = append(LeftPadWord256([]byte{0x5}).Bytes(), LeftPadWord256([]byte{0x6}).Bytes()...)
+	expected = []byte{0xb}
+	callCode(t, client, user[0].PubKey.Address(), code, data, expected)
+}
+
+func testCall(t *testing.T, typ string) {
+	wsc := newWSClient(t)
+	eid := types.EventStringNewBlock()
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+
+	client := clients[typ]
+
+	// create the contract
+	amt, gasLim, fee := int64(6969), int64(1000), int64(1000)
+	code, _, _ := simpleContract()
+	tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee)
+	receipt := broadcastTx(t, typ, tx)
+
+	if receipt.CreatesContract == 0 {
+		t.Fatal("This tx creates a contract")
+	}
+	if len(receipt.TxHash) == 0 {
+		t.Fatal("Failed to compute tx hash")
+	}
+	contractAddr := receipt.ContractAddr
+	if len(contractAddr) == 0 {
+		t.Fatal("Creates contract but resulting address is empty")
+	}
+
+	// allow it to get mined
+	waitForEvent(t, wsc, eid, true, func() {}, doNothing)
+	mempoolCount = 0
+
+	// run a call through the contract
+	data := []byte{}
+	expected := []byte{0xb}
+	callContract(t, client, user[0].PubKey.Address(), contractAddr, data, expected)
+}
+
+func testNameReg(t *testing.T, typ string) {
+	client := clients[typ]
+	wsc := newWSClient(t)
+
+	types.MinNameRegistrationPeriod = 1
+
+	// register a new name, check if its there
+	// since entries ought to be unique and these run against different clients, we append the typ
+	name := "ye_old_domain_name_" + typ
+	data := "if not now, when"
+	fee := int64(1000)
+	numDesiredBlocks := int64(2)
+	amt := fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
+
+	eid := types.EventStringNameReg(name)
+	subscribe(t, wsc, eid)
+
+	tx := makeDefaultNameTx(t, typ, name, data, amt, fee)
+	broadcastTx(t, typ, tx)
+	// verify the name by both using the event and by checking get_name
+	waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error {
+		// TODO: unmarshal the response
+		_ = b // TODO
+		tx, err := unmarshalResponseNameReg([]byte{})
+		if err != nil {
+			return err
+		}
+		if tx.Name != name {
+			t.Fatal(fmt.Sprintf("Err on received event tx.Name: Got %s, expected %s", tx.Name, name))
+		}
+		if tx.Data != data {
+			t.Fatal(fmt.Sprintf("Err on received event tx.Data: Got %s, expected %s", tx.Data, data))
+		}
+		return nil
+	})
+	mempoolCount = 0
+	entry := getNameRegEntry(t, typ, name)
+	if entry.Data != data {
+		t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
+	}
+	if bytes.Compare(entry.Owner, user[0].Address) != 0 {
+		t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address))
+	}
+
+	unsubscribe(t, wsc, eid)
+
+	// for the rest we just use new block event
+	// since we already tested the namereg event
+	eid = types.EventStringNewBlock()
+	subscribe(t, wsc, eid)
+	defer func() {
+		unsubscribe(t, wsc, eid)
+		wsc.Stop()
+	}()
+
+	// update the data as the owner, make sure still there
+	numDesiredBlocks = int64(2)
+	data = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need"
+	amt = fee + numDesiredBlocks*types.NameByteCostMultiplier*types.NameBlockCostMultiplier*types.NameBaseCost(name, data)
+	tx = makeDefaultNameTx(t, typ, name, data, amt, fee)
+	broadcastTx(t, typ, tx)
+	// commit block
+	waitForEvent(t, wsc, eid, true, func() {}, doNothing)
+	mempoolCount = 0
+	entry = getNameRegEntry(t, typ, name)
+	if entry.Data != data {
+		t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data))
+	}
+
+	// try to update as non owner, should fail
+	nonce := getNonce(t, typ, user[1].Address)
+	data2 := "this is not my beautiful house"
+	tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1)
+	tx.Sign(chainID, user[1])
+	_, err := edbcli.BroadcastTx(client, tx)
+	if err == nil {
+		t.Fatal("Expected error on NameTx")
+	}
+
+	// commit block
+	waitForEvent(t, wsc, eid, true, func() {}, doNothing)
+
+	// now the entry should be expired, so we can update as non owner
+	_, err = edbcli.BroadcastTx(client, tx)
+	waitForEvent(t, wsc, eid, true, func() {}, doNothing)
+	mempoolCount = 0
+	entry = getNameRegEntry(t, typ, name)
+	if entry.Data != data2 {
+		t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2))
+	}
+	if bytes.Compare(entry.Owner, user[1].Address) != 0 {
+		t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address))
+	}
+}
diff --git a/rpc/test/ws_helpers.go b/rpc/test/ws_helpers.go
new file mode 100644
index 00000000..5b0b88ed
--- /dev/null
+++ b/rpc/test/ws_helpers.go
@@ -0,0 +1,278 @@
+package rpctest
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"testing"
+	"time"
+
+	ctypes "github.com/eris-ltd/eris-db/rpc/core/types"
+	txtypes "github.com/eris-ltd/eris-db/txs"
+	"github.com/tendermint/tendermint/types"
+
+	"github.com/tendermint/go-events"
+	client "github.com/tendermint/go-rpc/client"
+	rpctypes "github.com/tendermint/go-rpc/types"
+	"github.com/tendermint/go-wire"
+)
+
+//--------------------------------------------------------------------------------
+// Utilities for testing the websocket service
+
+// create a new connection
+func newWSClient(t *testing.T) *client.WSClient {
+	wsc := client.NewWSClient(websocketAddr, websocketEndpoint)
+	if _, err := wsc.Start(); err != nil {
+		t.Fatal(err)
+	}
+	return wsc
+}
+
+// subscribe to an event
+func subscribe(t *testing.T, wsc *client.WSClient, eventid string) {
+	if err := wsc.Subscribe(eventid); err != nil {
+		t.Fatal(err)
+	}
+}
+
+// unsubscribe from an event
+func unsubscribe(t *testing.T, wsc *client.WSClient, eventid string) {
+	if err := wsc.Unsubscribe(eventid); err != nil {
+		t.Fatal(err)
+	}
+}
+
+// wait for an event; do things that might trigger events, and check them when they are received
+// the check function takes an event id and the byte slice read off the ws
+func waitForEvent(t *testing.T, wsc *client.WSClient, eventid string, dieOnTimeout bool, f func(), check func(string, interface{}) error) {
+	// go routine to wait for webscoket msg
+	goodCh := make(chan interface{})
+	errCh := make(chan error)
+
+	// Read message
+	go func() {
+		var err error
+	LOOP:
+		for {
+			select {
+			case r := <-wsc.ResultsCh:
+				result := new(ctypes.ErisDBResult)
+				wire.ReadJSONPtr(result, r, &err)
+				if err != nil {
+					errCh <- err
+					break LOOP
+				}
+				event, ok := (*result).(*ctypes.ResultEvent)
+				if ok && event.Event == eventid {
+					goodCh <- event.Data
+					break LOOP
+				}
+			case err := <-wsc.ErrorsCh:
+				errCh <- err
+				break LOOP
+			case <-wsc.Quit:
+				break LOOP
+			}
+		}
+	}()
+
+	// do stuff (transactions)
+	f()
+
+	// wait for an event or timeout
+	timeout := time.NewTimer(10 * time.Second)
+	select {
+	case <-timeout.C:
+		if dieOnTimeout {
+			wsc.Stop()
+			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 eventData := <-goodCh:
+		if dieOnTimeout {
+			// message was received and expected
+			// run the check
+			if err := check(eventid, eventData); err != nil {
+				t.Fatal(err) // Show the stack trace.
+			}
+		} else {
+			wsc.Stop()
+			t.Fatalf("%s event was not expected", eventid)
+		}
+	case err := <-errCh:
+		t.Fatal(err)
+		panic(err) // Show the stack trace.
+
+	}
+}
+
+//--------------------------------------------------------------------------------
+
+func unmarshalResponseNewBlock(b []byte) (*types.Block, error) {
+	// unmarshall and assert somethings
+	var response rpctypes.RPCResponse
+	var err error
+	wire.ReadJSON(&response, b, &err)
+	if err != nil {
+		return nil, err
+	}
+	if response.Error != "" {
+		return nil, fmt.Errorf(response.Error)
+	}
+	// TODO
+	//block := response.Result.(*ctypes.ResultEvent).Data.(types.EventDataNewBlock).Block
+	// return block, nil
+	return nil, nil
+}
+
+func unmarshalResponseNameReg(b []byte) (*txtypes.NameTx, error) {
+	// unmarshall and assert somethings
+	var response rpctypes.RPCResponse
+	var err error
+	wire.ReadJSON(&response, b, &err)
+	if err != nil {
+		return nil, err
+	}
+	if response.Error != "" {
+		return nil, fmt.Errorf(response.Error)
+	}
+	_, val := UnmarshalEvent(*response.Result)
+	tx := txtypes.DecodeTx(val.(types.EventDataTx).Tx).(*txtypes.NameTx)
+	return tx, nil
+}
+
+func unmarshalValidateBlockchain(t *testing.T, wsc *client.WSClient, eid string) {
+	var initBlockN int
+	for i := 0; i < 2; i++ {
+		waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error {
+			block, err := unmarshalResponseNewBlock(b.([]byte))
+			if err != nil {
+				return err
+			}
+			if i == 0 {
+				initBlockN = block.Header.Height
+			} else {
+				if block.Header.Height != initBlockN+i {
+					return fmt.Errorf("Expected block %d, got block %d", i, block.Header.Height)
+				}
+			}
+
+			return nil
+		})
+	}
+}
+
+func unmarshalValidateSend(amt int64, toAddr []byte) func(string, interface{}) error {
+	return func(eid string, b interface{}) error {
+		// unmarshal and assert correctness
+		var response rpctypes.RPCResponse
+		var err error
+		wire.ReadJSON(&response, b.([]byte), &err)
+		if err != nil {
+			return err
+		}
+		if response.Error != "" {
+			return fmt.Errorf(response.Error)
+		}
+		event, val := UnmarshalEvent(*response.Result)
+		if eid != event {
+			return fmt.Errorf("Eventid is not correct. Got %s, expected %s", event, eid)
+		}
+		tx := txtypes.DecodeTx(val.(types.EventDataTx).Tx).(*txtypes.SendTx)
+		if !bytes.Equal(tx.Inputs[0].Address, user[0].Address) {
+			return fmt.Errorf("Senders do not match up! Got %x, expected %x", tx.Inputs[0].Address, user[0].Address)
+		}
+		if tx.Inputs[0].Amount != amt {
+			return fmt.Errorf("Amt does not match up! Got %d, expected %d", tx.Inputs[0].Amount, amt)
+		}
+		if !bytes.Equal(tx.Outputs[0].Address, toAddr) {
+			return fmt.Errorf("Receivers do not match up! Got %x, expected %x", tx.Outputs[0].Address, user[0].Address)
+		}
+		return nil
+	}
+}
+
+func unmarshalValidateTx(amt int64, returnCode []byte) func(string, interface{}) error {
+	return func(eid string, b interface{}) error {
+		// unmarshall and assert somethings
+		var response rpctypes.RPCResponse
+		var err error
+		wire.ReadJSON(&response, b.([]byte), &err)
+		if err != nil {
+			return err
+		}
+		if response.Error != "" {
+			return fmt.Errorf(response.Error)
+		}
+		_, val := UnmarshalEvent(*response.Result)
+		var data = val.(txtypes.EventDataTx)
+		if data.Exception != "" {
+			return fmt.Errorf(data.Exception)
+		}
+		tx := data.Tx.(*txtypes.CallTx)
+		if !bytes.Equal(tx.Input.Address, user[0].Address) {
+			return fmt.Errorf("Senders do not match up! Got %x, expected %x",
+				tx.Input.Address, user[0].Address)
+		}
+		if tx.Input.Amount != amt {
+			return fmt.Errorf("Amt does not match up! Got %d, expected %d",
+				tx.Input.Amount, amt)
+		}
+		ret := data.Return
+		if !bytes.Equal(ret, returnCode) {
+			return fmt.Errorf("Tx did not return correctly. Got %x, expected %x", ret, returnCode)
+		}
+		return nil
+	}
+}
+
+func unmarshalValidateCall(origin, returnCode []byte, txid *[]byte) func(string, interface{}) error {
+	return func(eid string, b interface{}) error {
+		// unmarshall and assert somethings
+		var response rpctypes.RPCResponse
+		var err error
+		wire.ReadJSON(&response, b.([]byte), &err)
+		if err != nil {
+			return err
+		}
+		if response.Error != "" {
+			return fmt.Errorf(response.Error)
+		}
+		_, val := UnmarshalEvent(*response.Result)
+		var data = val.(txtypes.EventDataCall)
+		if data.Exception != "" {
+			return fmt.Errorf(data.Exception)
+		}
+		if !bytes.Equal(data.Origin, origin) {
+			return fmt.Errorf("Origin does not match up! Got %x, expected %x",
+				data.Origin, origin)
+		}
+		ret := data.Return
+		if !bytes.Equal(ret, returnCode) {
+			return fmt.Errorf("Call did not return correctly. Got %x, expected %x", ret, returnCode)
+		}
+		if !bytes.Equal(data.TxID, *txid) {
+			return fmt.Errorf("TxIDs do not match up! Got %x, expected %x",
+				data.TxID, *txid)
+		}
+		return nil
+	}
+}
+
+// Unmarshal a json event
+func UnmarshalEvent(b json.RawMessage) (string, events.EventData) {
+	var err error
+	result := new(ctypes.ErisDBResult)
+	wire.ReadJSONPtr(result, b, &err)
+	if err != nil {
+		panic(err)
+	}
+	event, ok := (*result).(*ctypes.ResultEvent)
+	if !ok {
+		return "", nil // TODO: handle non-event messages (ie. return from subscribe/unsubscribe)
+		// fmt.Errorf("Result is not type *ctypes.ResultEvent. Got %v", reflect.TypeOf(*result))
+	}
+	return event.Event, event.Data
+}
-- 
GitLab