From 9f696191f29a4b036d7c77d46df59797342db670 Mon Sep 17 00:00:00 2001
From: Silas Davis <silas@monax.io>
Date: Fri, 6 Apr 2018 18:23:07 +0100
Subject: [PATCH] Make sure TransactAndHold listens for root call to return.
 Fixes #715 on github.

Signed-off-by: Silas Davis <silas@monax.io>
---
 event/convention.go                     |  1 +
 execution/evm/events/events.go          | 27 +++++++++++++++++--------
 execution/evm/vm.go                     | 17 +++++++++++-----
 execution/evm/vm_test.go                |  3 ++-
 execution/transactor.go                 |  2 +-
 rpc/tm/integration/websocket_helpers.go |  4 ++--
 rpc/v0/client.go                        | 20 ++++++++++++++++++
 rpc/v0/integration/v0_test.go           | 21 ++++++++++++++-----
 8 files changed, 73 insertions(+), 22 deletions(-)

diff --git a/event/convention.go b/event/convention.go
index 450ebf73..33a66a50 100644
--- a/event/convention.go
+++ b/event/convention.go
@@ -11,6 +11,7 @@ const (
 	MessageTypeKey = "MessageType"
 	TxTypeKey      = "TxType"
 	TxHashKey      = "TxHash"
+	StackDepthKey  = "StackDepth"
 )
 
 // Get a query that matches events with a specific eventID
diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go
index af90a399..a9308bb5 100644
--- a/execution/evm/events/events.go
+++ b/execution/evm/events/events.go
@@ -33,11 +33,12 @@ func EventStringLogEvent(addr acm.Address) string    { return fmt.Sprintf("Log/%
 
 // EventDataCall fires when we call a contract, and when a contract calls another contract
 type EventDataCall struct {
-	CallData  *CallData
-	Origin    acm.Address
-	TxID      []byte
-	Return    []byte
-	Exception string
+	CallData   *CallData
+	Origin     acm.Address
+	TxHash     []byte
+	StackDepth int
+	Return     []byte
+	Exception  string
 }
 
 type CallData struct {
@@ -58,9 +59,11 @@ type EventDataLog struct {
 
 // Publish/Subscribe
 
-// Subscribe to account call event - if TxHash is provided listens for a specifc Tx otherwise captures all
+// Subscribe to account call event - if TxHash is provided listens for a specifc Tx otherwise captures all, if
+// stackDepth is greater than or equal to 0 captures calls at a specific stack depth (useful for capturing the return
+// of the root call over recursive calls
 func SubscribeAccountCall(ctx context.Context, subscribable event.Subscribable, subscriber string, address acm.Address,
-	txHash []byte, ch chan<- *EventDataCall) error {
+	txHash []byte, stackDepth int, ch chan<- *EventDataCall) error {
 
 	query := event.QueryForEventID(EventStringAccountCall(address))
 
@@ -68,6 +71,10 @@ func SubscribeAccountCall(ctx context.Context, subscribable event.Subscribable,
 		query = query.AndEquals(event.TxHashKey, hex.EncodeUpperToString(txHash))
 	}
 
+	if stackDepth >= 0 {
+		query = query.AndEquals(event.StackDepthKey, stackDepth)
+	}
+
 	return event.SubscribeCallback(ctx, subscribable, subscriber, query, func(message interface{}) bool {
 		eventDataCall, ok := message.(*EventDataCall)
 		if ok {
@@ -93,7 +100,11 @@ func SubscribeLogEvent(ctx context.Context, subscribable event.Subscribable, sub
 
 func PublishAccountCall(publisher event.Publisher, address acm.Address, eventDataCall *EventDataCall) error {
 	return event.PublishWithEventID(publisher, EventStringAccountCall(address), eventDataCall,
-		map[string]interface{}{"address": address, event.TxHashKey: hex.EncodeUpperToString(eventDataCall.TxID)})
+		map[string]interface{}{
+			"address":           address,
+			event.TxHashKey:     hex.EncodeUpperToString(eventDataCall.TxHash),
+			event.StackDepthKey: eventDataCall.StackDepth,
+		})
 }
 
 func PublishLogEvent(publisher event.Publisher, address acm.Address, eventDataLog *EventDataLog) error {
diff --git a/execution/evm/vm.go b/execution/evm/vm.go
index f94cf768..1837b47d 100644
--- a/execution/evm/vm.go
+++ b/execution/evm/vm.go
@@ -159,11 +159,18 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, ca
 	// fire the post call event (including exception if applicable)
 	if vm.publisher != nil {
 		events.PublishAccountCall(vm.publisher, calleeAddress, &events.EventDataCall{
-			&events.CallData{Caller: callerAddress, Callee: calleeAddress, Data: input, Value: value, Gas: *gas},
-			vm.origin,
-			vm.txHash,
-			*output,
-			*exception,
+			CallData: &events.CallData{
+				Caller: callerAddress,
+				Callee: calleeAddress,
+				Data:   input,
+				Value:  value,
+				Gas:    *gas,
+			},
+			Origin:    vm.origin,
+			TxHash:    vm.txHash,
+			StackDepth: vm.stackDepth,
+			Return:    *output,
+			Exception: *exception,
 		})
 	}
 }
diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go
index 82d79012..27ce0770 100644
--- a/execution/evm/vm_test.go
+++ b/execution/evm/vm_test.go
@@ -442,7 +442,8 @@ func runVM(eventCh chan<- *evm_events.EventDataCall, ourVm *VM, caller, callee a
 	emitter := event.NewEmitter(logging.NewNoopLogger())
 	fmt.Printf("subscribe to %s\n", subscribeAddr)
 
-	err := evm_events.SubscribeAccountCall(context.Background(), emitter, "test", subscribeAddr, nil, eventCh)
+	err := evm_events.SubscribeAccountCall(context.Background(), emitter, "test", subscribeAddr,
+		nil, -1, eventCh)
 	if err != nil {
 		return nil, err
 	}
diff --git a/execution/transactor.go b/execution/transactor.go
index ddbe8fe7..662407ec 100644
--- a/execution/transactor.go
+++ b/execution/transactor.go
@@ -276,7 +276,7 @@ func (trans *Transactor) TransactAndHold(privKey []byte, address *acm.Address, d
 	}
 
 	err = evm_events.SubscribeAccountCall(context.Background(), trans.eventEmitter, subID, receipt.ContractAddress,
-		receipt.TxHash, wc)
+		receipt.TxHash, 0, wc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/rpc/tm/integration/websocket_helpers.go b/rpc/tm/integration/websocket_helpers.go
index ec1c7ff2..cbaa5749 100644
--- a/rpc/tm/integration/websocket_helpers.go
+++ b/rpc/tm/integration/websocket_helpers.go
@@ -302,9 +302,9 @@ func unmarshalValidateCall(origin acm.Address, returnCode []byte, txid *[]byte)
 		if !bytes.Equal(ret, returnCode) {
 			return true, fmt.Errorf("call did not return correctly. Got %x, expected %x", ret, returnCode)
 		}
-		if !bytes.Equal(data.TxID, *txid) {
+		if !bytes.Equal(data.TxHash, *txid) {
 			return true, fmt.Errorf("TxIDs do not match up! Got %x, expected %x",
-				data.TxID, *txid)
+				data.TxHash, *txid)
 		}
 		return true, nil
 	}
diff --git a/rpc/v0/client.go b/rpc/v0/client.go
index 791f8931..8f635e17 100644
--- a/rpc/v0/client.go
+++ b/rpc/v0/client.go
@@ -7,7 +7,9 @@ import (
 	"net/http"
 	"time"
 
+	"github.com/hyperledger/burrow/execution/evm/events"
 	"github.com/hyperledger/burrow/rpc"
+	"github.com/hyperledger/burrow/txs"
 )
 
 type V0Client struct {
@@ -33,6 +35,24 @@ func NewV0Client(url string) *V0Client {
 	}
 }
 
+func (vc *V0Client) Transact(param TransactParam) (*txs.Receipt, error) {
+	receipt := new(txs.Receipt)
+	err := vc.Call(TRANSACT, param, receipt)
+	if err != nil {
+		return nil, err
+	}
+	return receipt, nil
+}
+
+func (vc *V0Client) TransactAndHold(param TransactParam) (*events.EventDataCall, error) {
+	eventDataCall := new(events.EventDataCall)
+	err := vc.Call(TRANSACT_AND_HOLD, param, eventDataCall)
+	if err != nil {
+		return nil, err
+	}
+	return eventDataCall, nil
+}
+
 func (vc *V0Client) Call(method string, param interface{}, result interface{}) error {
 	// Marhsal into JSONRPC request object
 	bs, err := vc.codec.EncodeBytes(param)
diff --git a/rpc/v0/integration/v0_test.go b/rpc/v0/integration/v0_test.go
index 433b2fae..91ef3715 100644
--- a/rpc/v0/integration/v0_test.go
+++ b/rpc/v0/integration/v0_test.go
@@ -21,25 +21,36 @@ import (
 	"testing"
 
 	"github.com/hyperledger/burrow/rpc/v0"
-	"github.com/hyperledger/burrow/txs"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestTransact(t *testing.T) {
 	cli := v0.NewV0Client("http://localhost:1337/rpc")
-	receipt := new(txs.Receipt)
 
 	address := privateAccounts[1].Address()
-	param := v0.TransactParam{
+	receipt, err := cli.Transact(v0.TransactParam{
 		PrivKey:  privateAccounts[0].PrivateKey().RawBytes(),
 		Address:  address.Bytes(),
 		Data:     []byte{},
 		Fee:      2,
 		GasLimit: 10000,
-	}
-	err := cli.Call(v0.TRANSACT, param, receipt)
+	})
 	require.NoError(t, err)
 	assert.False(t, receipt.CreatesContract)
 	assert.Equal(t, address, receipt.ContractAddress)
 }
+
+func TestTransactAndHold(t *testing.T) {
+	cli := v0.NewV0Client("http://localhost:1337/rpc")
+
+	call, err := cli.TransactAndHold(v0.TransactParam{
+		PrivKey:  privateAccounts[0].PrivateKey().RawBytes(),
+		Address:  nil,
+		Data:     []byte{},
+		Fee:      2,
+		GasLimit: 10000,
+	})
+	require.NoError(t, err)
+	assert.Equal(t, 0, call.StackDepth)
+}
-- 
GitLab