From 0050e4ec4b85ff9181e3e01729743b7227513f2b Mon Sep 17 00:00:00 2001
From: androlo <andreas@erisindustries.com>
Date: Tue, 4 Aug 2015 20:34:56 +0200
Subject: [PATCH] Tx and hold, 0.11.2

---
 api.md                              |  83 +++++++++++++++--
 erisdb/erisdbss/server_manager.go   |   6 ++
 erisdb/event_filters.go             |  49 ++++++++++
 erisdb/methods.go                   |  15 +++
 erisdb/middleware_test.go           |  16 ++--
 erisdb/pipe/accounts.go             |   4 +-
 erisdb/pipe/pipe.go                 |   1 +
 erisdb/pipe/transactor.go           |  57 +++++++++---
 erisdb/restServer.go                |  49 +++++++---
 erisdb/serve.go                     |   2 +-
 test/mock/pipe.go                   |   4 +
 test/transacting/transacting_tes.go | 139 ++++++++++++++++++++++++++++
 test/web_api/query_test.go          |   2 +-
 13 files changed, 383 insertions(+), 44 deletions(-)
 create mode 100644 erisdb/event_filters.go
 create mode 100644 test/transacting/transacting_tes.go

diff --git a/api.md b/api.md
index 53ffe63c..5af0164e 100644
--- a/api.md
+++ b/api.md
@@ -145,7 +145,7 @@ The corresponding Ed25519 private key: `[1, "6B72D45EB65F619F11CE580C8CAED9E0BAD
 <a name="the-transaction-types"></a>
 ###The transaction types
 
-These are the types of transactions:
+These are the types of transactions. Note that in DApp programming you would only use the `CallTx`, and maybe `NameTx`.
 
 ####SendTx
 
@@ -312,13 +312,15 @@ Event object:
 <Tx>
 ```
 
-#### Account Receive
+#### Account Call
 
-This notifies you when an account is the target of a call, like when calling an accessor function.
+This notifies you when an account is the target of a call. This event is emitted when `CallTx`s (transactions) that target the given account has been finalized. It is possible to listen to this event when creating new contracts as well; it will fire when the transaction is committed (or not, in which case the 'exception' field will explain why it failed). 
 
-Event ID: `Acc/<address>/Receive`
+**NOTE: The naming here is a bit unfortunate. Ethereum uses 'transaction' for (state-changing) transactions to a contract account, and 'call' for read-only calls like is used for accessor functions and such. Tendermint on the other hand, which uses many types of transactions uses 'CallTx' for a transaction made to a contract account, since it calls the code in that contract, and refers to these simply as 'calls'. Read-only calls is normally referred to as 'simulated calls'.** 
 
-Example: `Acc/B4F9DA82738D37A1D83AD2CDD0C0D3CBA76EA4E7/Input` will subscribe to call receive events from the account with address: B4F9DA82738D37A1D83AD2CDD0C0D3CBA76EA4E7.
+Event ID: `Acc/<address>/Call`
+
+Example: `Acc/B4F9DA82738D37A1D83AD2CDD0C0D3CBA76EA4E7/Call` will subscribe to events from the account with address: B4F9DA82738D37A1D83AD2CDD0C0D3CBA76EA4E7.
 
 
 ```
@@ -525,18 +527,17 @@ NOTE: Get peer is not fully implemented.
 | [BroadcastTx](#broadcast-tx) | erisdb.broadcastTx | POST | `/txpool` |
 | [GetUnconfirmedTxs](#get-unconfirmed-txs) | erisdb.getUnconfirmedTxs | GET | `/txpool` |
 
-
 ###Code execution
 | Name | RPC method name | HTTP method | HTTP endpoint |
 | :--- | :-------------- | :---------: | :------------ |
 | [Call](#call) | erisdb.call | POST | `/calls` |
 | [CallCode](#call-code) | erisdb.callCode | POST | `/codecalls` |
 
-
 ####Unsafe
 | Name | RPC method name | HTTP method | HTTP endpoint |
 | :--- | :-------------- | :---------: | :------------ |
 | [Transact](#transact) | erisdb.transact | POST | `/unsafe/txpool` |
+| [Transact](#transact-and-hold) | erisdb.transactAndHold | POST | `/unsafe/txpool?hold=true` |
 | [TransactNameReg](#transact-name-reg) | erisdb.transactNameReg | POST | `/unsafe/namereg/txpool` |
 | [GenPrivAccount](#gen-priv-account) | erisdb.genPrivAccount | GET | `/unsafe/pa_generator` |
 
@@ -1812,8 +1813,76 @@ The same as with BroadcastTx:
 
 See [The transaction types](#the-transaction-types) for more info on the `CallTx` type. 
 
+If you want to hold the tx, use `/unsafe/txpool?hold=true`. See `TransactAndHold` below.
+
 ***
 
+<a name="transact-and-hold"></a>
+####TransactAndHold
+
+Convenience method for sending a transaction and holding until it's been committed (or not). It will do the following things:
+
+* Use the private key to create a private account object (i.e. generate public key and address).
+* Use the other parameters to create a `CallTx` object.
+* Sign the transaction.
+* Broadcast the transaction.
+* Wait until the transaction is fully processed. 
+
+When holding, the request will eventually timeout if the transaction is not processed nor produce an error. The response will then be an error that includes the transaction hash (which can be used for further investigation).
+
+#####HTTP
+
+Method: POST
+
+Endpoint: `/unsafe/txpool`
+
+Query: `?hold=true`
+
+Body: See JSON-RPC parameters.
+
+#####JSON-RPC
+
+Method: `erisdb.transactAndHold`
+
+Parameters: 
+
+```
+{
+	priv_key:  <string>
+	data:      <string>
+	address:   <string>
+	fee:       <number>
+	gas_limit: <number>
+}
+```
+
+private key is the hex string only.
+
+#####Return value
+
+```
+{
+	call_data: {
+		caller: <string>
+    	callee: <string>
+    	data:   <string>
+    	value:  <number>
+    	gas:    <number>
+	}
+	origin:     <string>
+	tx_id:      <string>
+	return:     <string>
+	exception:  <string>
+}
+```
+
+#####Additional info
+
+See [The transaction types](#the-transaction-types) for more info on the `CallTx` type. 
+
+If you don't want to hold the tx, either use `/unsafe/txpool?hold=false` or omit the query entirely. See `Transact` for the regular version.
+
+***
 
 <a name="transact-name-reg"></a>
 ####TransactNameReg
diff --git a/erisdb/erisdbss/server_manager.go b/erisdb/erisdbss/server_manager.go
index 59d33405..701e8071 100644
--- a/erisdb/erisdbss/server_manager.go
+++ b/erisdb/erisdbss/server_manager.go
@@ -63,6 +63,12 @@ func (this *CmdProcess) Start(doneChan chan<- error) {
 		log.Debug(text)
 		if strings.Index(text, this.token) != -1 {
 			log.Debug("Token found", "token", this.token)
+			go func(){
+				for scanner.Scan() {
+					text := scanner.Text()
+					log.Debug(text)
+				}
+			}()
 			break
 		}
 	}
diff --git a/erisdb/event_filters.go b/erisdb/event_filters.go
new file mode 100644
index 00000000..757a1ccf
--- /dev/null
+++ b/erisdb/event_filters.go
@@ -0,0 +1,49 @@
+package erisdb
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	ep "github.com/eris-ltd/eris-db/erisdb/pipe"
+	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types"
+)
+
+// Filter for account code.
+// Ops: == or !=
+// Could be used to match against nil, to see if an account is a contract account.
+type AccountCallTxHashFilter struct {
+	op    string
+	value []byte
+	match func([]byte, []byte) bool
+}
+
+func (this *AccountCallTxHashFilter) Configure(fd *ep.FilterData) error {
+	op := fd.Op
+	val, err := hex.DecodeString(fd.Value)
+
+	if err != nil {
+		return fmt.Errorf("Wrong value type.")
+	}
+	if op == "==" {
+		this.match = func(a, b []byte) bool {
+			return bytes.Equal(a, b)
+		}
+	} else if op == "!=" {
+		this.match = func(a, b []byte) bool {
+			return !bytes.Equal(a, b)
+		}
+	} else {
+		return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering")
+	}
+	this.op = op
+	this.value = val
+	return nil
+}
+
+func (this *AccountCallTxHashFilter) Match(v interface{}) bool {
+	emct, ok := v.(*types.EventMsgCall)
+	if !ok {
+		return false
+	}
+	return this.match(emct.TxID, this.value)
+}
\ No newline at end of file
diff --git a/erisdb/methods.go b/erisdb/methods.go
index 8bd12e3c..13fe8744 100644
--- a/erisdb/methods.go
+++ b/erisdb/methods.go
@@ -42,6 +42,7 @@ const (
 	GET_UNCONFIRMED_TXS       = SERVICE_NAME + ".getUnconfirmedTxs"
 	SIGN_TX                   = SERVICE_NAME + ".signTx"
 	TRANSACT                  = SERVICE_NAME + ".transact"
+	TRANSACT_AND_HOLD         = SERVICE_NAME + ".transactAndHold"
 	TRANSACT_NAMEREG          = SERVICE_NAME + ".transactNameReg"
 	EVENT_SUBSCRIBE           = SERVICE_NAME + ".eventSubscribe" // Events
 	EVENT_UNSUBSCRIBE         = SERVICE_NAME + ".eventUnsubscribe"
@@ -95,6 +96,7 @@ func (this *ErisDbMethods) getMethods() map[string]RequestHandlerFunc {
 	dhMap[GET_UNCONFIRMED_TXS] = this.UnconfirmedTxs
 	dhMap[SIGN_TX] = this.SignTx
 	dhMap[TRANSACT] = this.Transact
+	dhMap[TRANSACT_AND_HOLD] = this.TransactAndHold
 	dhMap[TRANSACT_NAMEREG] = this.TransactNameReg
 	// Namereg
 	dhMap[GET_NAMEREG_ENTRY] = this.NameRegEntry
@@ -403,6 +405,19 @@ func (this *ErisDbMethods) Transact(request *rpc.RPCRequest, requester interface
 	return receipt, 0, nil
 }
 
+func (this *ErisDbMethods) TransactAndHold(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) {
+	param := &TransactParam{}
+	err := this.codec.DecodeBytes(param, request.Params)
+	if err != nil {
+		return nil, rpc.INVALID_PARAMS, err
+	}
+	ce, errC := this.pipe.Transactor().TransactAndHold(param.PrivKey, param.Address, param.Data, param.GasLimit, param.Fee)
+	if errC != nil {
+		return nil, rpc.INTERNAL_ERROR, errC
+	}
+	return ce, 0, nil
+}
+
 func (this *ErisDbMethods) TransactNameReg(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) {
 	param := &TransactNameRegParam{}
 	err := this.codec.DecodeBytes(param, request.Params)
diff --git a/erisdb/middleware_test.go b/erisdb/middleware_test.go
index 6a40a277..2774abb2 100644
--- a/erisdb/middleware_test.go
+++ b/erisdb/middleware_test.go
@@ -8,20 +8,20 @@ import (
 
 // Test empty query.
 func TestEmptyQuery(t *testing.T) {
-	arr, err := _parseQuery("")
+	arr, err := _parseSearchQuery("")
 	assert.NoError(t, err)
 	assert.Nil(t, arr, "Array should be nil")
 }
 
 // Test no colon separated filter.
 func TestQueryNoColonSeparator(t *testing.T) {
-	_, err := _parseQuery("test")
+	_, err := _parseSearchQuery("test")
 	assert.Error(t, err, "Should detect missing colon.")
 }
 
 // Test no colon separated filter and proper filters mixed.
 func TestQueryNoColonSeparatorMulti(t *testing.T) {
-	_, err := _parseQuery("test test1:24 test2")
+	_, err := _parseSearchQuery("test test1:24 test2")
 	assert.Error(t, err, "Should detect missing colon.")
 }
 
@@ -67,7 +67,7 @@ func TestQueryNEQ(t *testing.T) {
 
 func TestCombined(t *testing.T) {
 	q := "balance:>=5 sequence:<8"
-	arr, err := _parseQuery(q)
+	arr, err := _parseSearchQuery(q)
 	assert.NoError(t, err)
 	assert.Len(t, arr, 2)
 	f0 := arr[0]
@@ -103,13 +103,13 @@ func TestRangeQueryWildcardULB(t *testing.T) {
 
 // Test a range query with no upper bounds term.
 func TestRangeQueryBotchedMax(t *testing.T) {
-	_, err := _parseQuery("test:5..")
+	_, err := _parseSearchQuery("test:5..")
 	assert.Error(t, err, "Malformed range-query passed")
 }
 
 // Test a range query with no lower bounds term.
 func TestRangeQueryBotchedMin(t *testing.T) {
-	_, err := _parseQuery("test:..5")
+	_, err := _parseSearchQuery("test:..5")
 	assert.Error(t, err, "Malformed range-query passed")
 }
 
@@ -120,7 +120,7 @@ func testOp(op string, t *testing.T) {
 }
 
 func assertFilter(t *testing.T, filter, field, op, val string) {
-	arr, err := _parseQuery(filter)
+	arr, err := _parseSearchQuery(filter)
 	assert.NoError(t, err)
 	assert.NotNil(t, arr)
 	assert.Len(t, arr, 1)
@@ -128,7 +128,7 @@ func assertFilter(t *testing.T, filter, field, op, val string) {
 }
 
 func assertRangeFilter(t *testing.T, min, max, res0, res1 string) {
-	arr, err := _parseQuery("test:" + min + ".." + max)
+	arr, err := _parseSearchQuery("test:" + min + ".." + max)
 	assert.NoError(t, err)
 	assert.NotNil(t, arr)
 	assert.Len(t, arr, 2)
diff --git a/erisdb/pipe/accounts.go b/erisdb/pipe/accounts.go
index 1afc8c3c..50ceab02 100644
--- a/erisdb/pipe/accounts.go
+++ b/erisdb/pipe/accounts.go
@@ -49,9 +49,7 @@ func (this *accounts) GenPrivAccountFromKey(privKey []byte) (*account.PrivAccoun
 		return nil, fmt.Errorf("Private key is not 64 bytes long.")
 	}
 	pk := &[64]byte{}
-	for i := 0; i < 64; i++ {
-		pk[i] = privKey[i]
-	}
+	copy(pk[:], privKey)
 	fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", pk)
 	pa := account.GenPrivAccountFromPrivKeyBytes(pk)
 	return pa, nil
diff --git a/erisdb/pipe/pipe.go b/erisdb/pipe/pipe.go
index 29c59f3d..aad6a45f 100644
--- a/erisdb/pipe/pipe.go
+++ b/erisdb/pipe/pipe.go
@@ -70,6 +70,7 @@ type (
 		CallCode(fromAddress, code, data []byte) (*Call, error)
 		BroadcastTx(tx types.Tx) (*Receipt, error)
 		Transact(privKey, address, data []byte, gasLimit, fee int64) (*Receipt, error)
+		TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*types.EventMsgCall, error)
 		TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*Receipt, error)
 		UnconfirmedTxs() (*UnconfirmedTxs, error)
 		SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error)
diff --git a/erisdb/pipe/transactor.go b/erisdb/pipe/transactor.go
index 1103de32..19b9c7b4 100644
--- a/erisdb/pipe/transactor.go
+++ b/erisdb/pipe/transactor.go
@@ -1,6 +1,7 @@
 package pipe
 
 import (
+	"bytes"
 	"encoding/hex"
 	"fmt"
 	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/account"
@@ -11,12 +12,7 @@ import (
 	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/state"
 	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types"
 	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/vm"
-)
-
-const (
-	DEFAULT_BLOCKS_WAIT = 10
-	SUB_ID              = "TransactorSubBlock"
-	EVENT_ID            = "NewBlock"
+	"time"
 )
 
 type transactor struct {
@@ -135,9 +131,7 @@ func (this *transactor) Transact(privKey, address, data []byte, gasLimit, fee in
 		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
 	}
 	pk := &[64]byte{}
-	for i := 0; i < 64; i++ {
-		pk[i] = privKey[i]
-	}
+	copy(pk[:], privKey)
 	fmt.Printf("PK BYTES FROM TRANSACT: %x\n", pk)
 	pa := account.GenPrivAccountFromPrivKeyBytes(pk)
 	cache := this.mempoolReactor.Mempool.GetCache()
@@ -169,15 +163,54 @@ func (this *transactor) Transact(privKey, address, data []byte, gasLimit, fee in
 	return this.BroadcastTx(txS)
 }
 
+func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*types.EventMsgCall, error) {
+	rec, tErr := this.Transact(privKey, address, data, gasLimit, fee)
+	if tErr != nil {
+		return nil, tErr
+	}
+	var addr []byte
+	if rec.CreatesContract == 1 {
+		addr = rec.ContractAddr
+	} else {
+		addr = address
+	}
+	wc := make(chan *types.EventMsgCall)
+	subId := fmt.Sprintf("%X", rec.TxHash)
+	this.eventEmitter.Subscribe(subId, types.EventStringAccCall(addr), func(evt interface{}) {
+		event := evt.(types.EventMsgCall)
+		if bytes.Equal(event.TxID, rec.TxHash) {
+			wc <- &event
+		}
+	})
+	
+	timer := time.NewTimer(10 * time.Second)
+	toChan := timer.C
+	
+	var ret *types.EventMsgCall
+	var rErr error
+	 
+	select {
+	case <-toChan:
+		rErr = fmt.Errorf("Transaction timed out. Hash: " + subId)
+	case e := <-wc:
+		timer.Stop()
+		if e.Exception != "" {
+			rErr = fmt.Errorf("Error when transacting: " + e.Exception)
+		} else {
+			ret = e
+		}
+	}
+	this.eventEmitter.Unsubscribe(subId)
+	return ret, rErr
+}
+
 func (this *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*Receipt, error) {
 
 	if len(privKey) != 64 {
 		return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey))
 	}
 	pk := &[64]byte{}
-	for i := 0; i < 64; i++ {
-		pk[i] = privKey[i]
-	}
+	copy(pk[:], privKey)
 	fmt.Printf("PK BYTES FROM TRANSACT NAMEREG: %x\n", pk)
 	pa := account.GenPrivAccountFromPrivKeyBytes(pk)
 	cache := this.mempoolReactor.Mempool.GetCache()
diff --git a/erisdb/restServer.go b/erisdb/restServer.go
index 96ad35e0..e9131b0e 100644
--- a/erisdb/restServer.go
+++ b/erisdb/restServer.go
@@ -31,7 +31,7 @@ func NewRestServer(codec rpc.Codec, pipe ep.Pipe, eventSubs *EventSubscriptions)
 // Starting the server means registering all the handlers with the router.
 func (this *RestServer) Start(config *server.ServerConfig, router *gin.Engine) {
 	// Accounts
-	router.GET("/accounts", parseQuery, this.handleAccounts)
+	router.GET("/accounts", parseSearchQuery, this.handleAccounts)
 	router.GET("/accounts/:address", addressParam, this.handleAccount)
 	router.GET("/accounts/:address/storage", addressParam, this.handleStorage)
 	router.GET("/accounts/:address/storage/:key", addressParam, keyParam, this.handleStorageAt)
@@ -41,7 +41,7 @@ func (this *RestServer) Start(config *server.ServerConfig, router *gin.Engine) {
 	router.GET("/blockchain/genesis_hash", this.handleGenesisHash)
 	router.GET("/blockchain/latest_block_height", this.handleLatestBlockHeight)
 	router.GET("/blockchain/latest_block", this.handleLatestBlock)
-	router.GET("/blockchain/blocks", parseQuery, this.handleBlocks)
+	router.GET("/blockchain/blocks", parseSearchQuery, this.handleBlocks)
 	router.GET("/blockchain/block/:height", heightParam, this.handleBlock)
 	// Consensus
 	router.GET("/consensus", this.handleConsensusState)
@@ -51,7 +51,7 @@ func (this *RestServer) Start(config *server.ServerConfig, router *gin.Engine) {
 	router.GET("/event_subs/:id", this.handleEventPoll)
 	router.DELETE("/event_subs/:id", this.handleEventUnsubscribe)
 	// NameReg
-	router.GET("/namereg", parseQuery, this.handleNameRegEntries)
+	router.GET("/namereg", parseSearchQuery, this.handleNameRegEntries)
 	router.GET("/namereg/:key", nameParam, this.handleNameRegEntry)
 	// Network
 	router.GET("/network", this.handleNetworkInfo)
@@ -69,7 +69,7 @@ func (this *RestServer) Start(config *server.ServerConfig, router *gin.Engine) {
 	router.POST("/codecalls", this.handleCallCode)
 	// Unsafe
 	router.GET("/unsafe/pa_generator", this.handleGenPrivAcc)
-	router.POST("/unsafe/txpool", this.handleTransact)
+	router.POST("/unsafe/txpool", parseTxModifier, this.handleTransact)
 	router.POST("/unsafe/namereg/txpool", this.handleTransactNameReg)
 	router.POST("/unsafe/tx_signer", this.handleSignTx)
 	this.running = true
@@ -424,17 +424,29 @@ func (this *RestServer) handleCallCode(c *gin.Context) {
 }
 
 func (this *RestServer) handleTransact(c *gin.Context) {
+	
+	_, hold := c.Get("hold")
+	
 	param := &TransactParam{}
 	errD := this.codec.Decode(param, c.Request.Body)
 	if errD != nil {
 		c.AbortWithError(500, errD)
 	}
-	receipt, err := this.pipe.Transactor().Transact(param.PrivKey, param.Address, param.Data, param.GasLimit, param.Fee)
-	if err != nil {
-		c.AbortWithError(500, err)
+	if hold {
+		res, err := this.pipe.Transactor().TransactAndHold(param.PrivKey, param.Address, param.Data, param.GasLimit, param.Fee)
+		if err != nil {
+			c.AbortWithError(500, err)
+		}
+		c.Writer.WriteHeader(200)
+		this.codec.Encode(res, c.Writer)
+	} else {
+		receipt, err := this.pipe.Transactor().Transact(param.PrivKey, param.Address, param.Data, param.GasLimit, param.Fee)
+		if err != nil {
+			c.AbortWithError(500, err)
+		}
+		c.Writer.WriteHeader(200)
+		this.codec.Encode(receipt, c.Writer)
 	}
-	c.Writer.WriteHeader(200)
-	this.codec.Encode(receipt, c.Writer)
 }
 
 func (this *RestServer) handleTransactNameReg(c *gin.Context) {
@@ -521,10 +533,23 @@ func peerAddressParam(c *gin.Context) {
 	c.Next()
 }
 
-func parseQuery(c *gin.Context) {
+func parseTxModifier(c *gin.Context) {
+	hold := c.Query("hold")
+	if hold == "true" {
+		c.Set("hold", true)
+	} else if (hold != "") {
+		if hold != "false" {
+			c.Writer.WriteHeader(400)
+			c.Writer.Write([]byte("tx hold must be either 'true' or 'false', found: " + hold))
+			c.Abort()
+		}
+	}
+}
+
+func parseSearchQuery(c *gin.Context) {
 	q := c.Query("q")
 	if q != "" {
-		data, err := _parseQuery(q)
+		data, err := _parseSearchQuery(q)
 		if err != nil {
 			c.Writer.WriteHeader(400)
 			c.Writer.Write([]byte(err.Error()))
@@ -536,7 +561,7 @@ func parseQuery(c *gin.Context) {
 	}
 }
 
-func _parseQuery(queryString string) ([]*ep.FilterData, error) {
+func _parseSearchQuery(queryString string) ([]*ep.FilterData, error) {
 	if len(queryString) == 0 {
 		return nil, nil
 	}
diff --git a/erisdb/serve.go b/erisdb/serve.go
index 713cbe91..7fdd1eb6 100644
--- a/erisdb/serve.go
+++ b/erisdb/serve.go
@@ -14,7 +14,7 @@ import (
 	"path"
 )
 
-const ERISDB_VERSION = "0.11.1"
+const ERISDB_VERSION = "0.11.2"
 const TENDERMINT_VERSION = "0.5.0"
 
 var log = log15.New("module", "eris/erisdb_server")
diff --git a/test/mock/pipe.go b/test/mock/pipe.go
index 3f331ad8..f2b7f2a5 100644
--- a/test/mock/pipe.go
+++ b/test/mock/pipe.go
@@ -239,6 +239,10 @@ func (this *transactor) Transact(privKey, address, data []byte, gasLimit, fee in
 	return this.testData.Transact.Output, nil
 }
 
+func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*types.EventMsgCall, error) {
+	return nil, nil
+}
+
 func (this *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*ep.Receipt, error) {
 	return this.testData.TransactNameReg.Output, nil
 }
diff --git a/test/transacting/transacting_tes.go b/test/transacting/transacting_tes.go
new file mode 100644
index 00000000..a1f0da1a
--- /dev/null
+++ b/test/transacting/transacting_tes.go
@@ -0,0 +1,139 @@
+package transacting
+
+// Basic imports
+import (
+	"bytes"
+	"fmt"
+	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/stretchr/testify/suite"
+	// "github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/tendermint/types"
+	edb "github.com/eris-ltd/eris-db/erisdb"
+	ess "github.com/eris-ltd/eris-db/erisdb/erisdbss"
+	// ep "github.com/eris-ltd/eris-db/erisdb/pipe"
+	"github.com/eris-ltd/eris-db/rpc"
+	"github.com/eris-ltd/eris-db/server"
+	td "github.com/eris-ltd/eris-db/test/testdata/testdata"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+	"testing"
+	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/gin-gonic/gin"
+	"github.com/eris-ltd/eris-db/Godeps/_workspace/src/github.com/tendermint/log15"
+	"runtime"
+)
+
+func init() {
+	runtime.GOMAXPROCS(runtime.NumCPU())
+	log15.Root().SetHandler(log15.LvlFilterHandler(
+		log15.LvlInfo,
+		log15.StreamHandler(os.Stdout, log15.TerminalFormat()),
+	))
+	gin.SetMode(gin.ReleaseMode)
+}
+
+const (
+	TX_URL        = "http://localhost:31405/server"
+	CONTRACT_CODE = "60606040525b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b609480603e6000396000f30060606040523615600d57600d565b60685b6000600060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050805033600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055505b90565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f3"
+)
+
+func getCreateInput(privKey [64]byte) *edb.TransactParam {
+	tp := &edb.TransactParam{}
+	tp.PrivKey = privKey[:]
+	tp.Address = nil
+	tp.Data = []byte(CONTRACT_CODE)
+	tp.GasLimit = 100000
+	tp.Fee = 0
+	return tp
+}
+
+type TxSuite struct {
+	suite.Suite
+	baseDir      string
+	serveProcess *server.ServeProcess
+	codec        rpc.Codec
+	sUrl         string
+	testData     *td.TestData
+}
+
+func (this *TxSuite) SetupSuite() {
+	baseDir := path.Join(os.TempDir(), "/.edbservers")
+	ss := ess.NewServerServer(baseDir)
+	cfg := server.DefaultServerConfig()
+	cfg.Bind.Port = uint16(31405)
+	proc := server.NewServeProcess(cfg, ss)
+	err := proc.Start()
+	if err != nil {
+		panic(err)
+	}
+	this.serveProcess = proc
+	testData := td.LoadTestData()
+	this.codec = edb.NewTCodec()
+
+	requestData := &ess.RequestData{testData.ChainData.PrivValidator, testData.ChainData.Genesis, 30}
+	rBts, _ := this.codec.EncodeBytes(requestData)
+	resp, _ := http.Post(TX_URL, "application/json", bytes.NewBuffer(rBts))
+	if resp.StatusCode != 200 {
+		bts, _ := ioutil.ReadAll(resp.Body)
+		fmt.Println("ERROR GETTING SS ADDRESS: " + string(bts))
+		fmt.Printf("%v\n", resp)
+		panic(fmt.Errorf(string(bts)))
+	}
+	rd := &ess.ResponseData{}
+	err2 := this.codec.Decode(rd, resp.Body)
+	if err2 != nil {
+		panic(err2)
+	}
+	fmt.Println("Received Port: " + rd.Port)
+	this.sUrl = "http://localhost:" + rd.Port
+	fmt.Println("URL: " + this.sUrl)
+	this.testData = testData
+}
+
+func (this *TxSuite) TearDownSuite() {
+	sec := this.serveProcess.StopEventChannel()
+	this.serveProcess.Stop(0)
+	<-sec
+}
+
+// ********************************************* Tests *********************************************
+
+// TODO less duplication.
+func (this *TxSuite) Test_A0_Tx_Create() {
+	input := getCreateInput([64]byte(this.testData.ChainData.PrivValidator.PrivKey))
+	resp := this.postJson("/unsafe/txpool?hold=true", input)
+	bts, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		panic(err)
+	} else {
+		fmt.Printf("%s\n", string(bts))
+	}
+	//ret := &types.EventMsgCall{}
+	// errD := this.codec.Decode(ret, resp.Body)
+	//this.NoError(errD)
+	//json, _ := this.codec.EncodeBytes(ret)
+	//fmt.Printf("%s\n", string(json))
+}
+
+// ********************************************* Utilities *********************************************
+
+func (this *TxSuite) get(endpoint string) *http.Response {
+	resp, errG := http.Get(this.sUrl + endpoint)
+	this.NoError(errG)
+	this.Equal(200, resp.StatusCode)
+	return resp
+}
+
+func (this *TxSuite) postJson(endpoint string, v interface{}) *http.Response {
+	bts, errE := this.codec.EncodeBytes(v)
+	this.NoError(errE)
+	resp, errP := http.Post(this.sUrl+endpoint, "application/json", bytes.NewBuffer(bts))
+	this.NoError(errP)
+	this.Equal(200, resp.StatusCode)
+	return resp
+}
+
+// ********************************************* Entrypoint *********************************************
+
+func TestQuerySuite(t *testing.T) {
+	suite.Run(t, &TxSuite{})
+}
diff --git a/test/web_api/query_test.go b/test/web_api/query_test.go
index 3acb0a7e..ae180615 100644
--- a/test/web_api/query_test.go
+++ b/test/web_api/query_test.go
@@ -133,4 +133,4 @@ func generateQuery(fda []*ep.FilterData) string {
 
 func TestQuerySuite(t *testing.T) {
 	suite.Run(t, &QuerySuite{})
-}
+}
\ No newline at end of file
-- 
GitLab