From 650d75337f2fb44f7b62b5d8dc7158cb954a1f16 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 24 Jul 2020 15:41:42 +0300 Subject: [PATCH 1/3] Wrap VMOutput in a JSON-friendly structure. --- go.mod | 1 + ipc/common/messagesBlockchain.go | 1 + ipc/common/messagesContracts.go | 4 +- ipc/common/messages_test.go | 85 +++++++++++++++++++++ ipc/common/serializableVMOutput.go | 118 +++++++++++++++++++++++++++++ ipc/nodepart/arwenDriver.go | 4 +- 6 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 ipc/common/messages_test.go create mode 100644 ipc/common/serializableVMOutput.go diff --git a/go.mod b/go.mod index 6f8bf1dd5..dcba56a4a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/ElrondNetwork/elrond-go-logger v1.0.2 github.com/ElrondNetwork/elrond-vm-common v0.1.23 github.com/ElrondNetwork/elrond-vm-util v0.3.5 + github.com/davecgh/go-spew v1.1.1 github.com/gin-gonic/gin v1.6.3 github.com/mitchellh/mapstructure v1.1.2 github.com/pelletier/go-toml v1.6.0 diff --git a/ipc/common/messagesBlockchain.go b/ipc/common/messagesBlockchain.go index d6f092f64..7d1de94d4 100644 --- a/ipc/common/messagesBlockchain.go +++ b/ipc/common/messagesBlockchain.go @@ -469,6 +469,7 @@ func NewMessageBlockchainGetAllStateRequest(address []byte) *MessageBlockchainGe // MessageBlockchainGetAllStateResponse represents a response message type MessageBlockchainGetAllStateResponse struct { Message + // TODO: Make sure to change "map[string][]byte" to a JSON-serializable friendly structure when "GetAllState()" will be used AllState map[string][]byte } diff --git a/ipc/common/messagesContracts.go b/ipc/common/messagesContracts.go index c65c9b1e5..a5c63c1b3 100644 --- a/ipc/common/messagesContracts.go +++ b/ipc/common/messagesContracts.go @@ -35,14 +35,14 @@ func NewMessageContractCallRequest(input *vmcommon.ContractCallInput) *MessageCo // MessageContractResponse is a contract response message (from Arwen) type MessageContractResponse struct { Message - VMOutput *vmcommon.VMOutput + SerializableVMOutput *SerializableVMOutput } // NewMessageContractResponse creates a message func NewMessageContractResponse(vmOutput *vmcommon.VMOutput, err error) *MessageContractResponse { message := &MessageContractResponse{} message.Kind = ContractResponse - message.VMOutput = vmOutput + message.SerializableVMOutput = NewSerializableVMOutput(vmOutput) message.SetError(err) return message } diff --git a/ipc/common/messages_test.go b/ipc/common/messages_test.go new file mode 100644 index 000000000..9c392370a --- /dev/null +++ b/ipc/common/messages_test.go @@ -0,0 +1,85 @@ +package common + +import ( + "fmt" + "reflect" + "testing" + + "github.com/ElrondNetwork/arwen-wasm-vm/ipc/marshaling" + vmcommon "github.com/ElrondNetwork/elrond-vm-common" + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" +) + +func TestMessageContractResponse_IsConsistentlySerializable(t *testing.T) { + vmOutput := &vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} + vmOutput.OutputAccounts["alice"] = &vmcommon.OutputAccount{StorageUpdates: make(map[string]*vmcommon.StorageUpdate)} + vmOutput.OutputAccounts["alice"].StorageUpdates["foo"] = &vmcommon.StorageUpdate{} + vmOutput.OutputAccounts["alice"].StorageUpdates["bar"] = &vmcommon.StorageUpdate{} + message := NewMessageContractResponse(vmOutput, nil) + requireSerializationConsistency(t, message, &MessageContractResponse{}) + + // Non text as output account keys + vmOutput = &vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} + vmOutput.OutputAccounts[string([]byte{0})] = &vmcommon.OutputAccount{StorageUpdates: make(map[string]*vmcommon.StorageUpdate)} + vmOutput.OutputAccounts[string([]byte{0})].StorageUpdates["foo"] = &vmcommon.StorageUpdate{} + vmOutput.OutputAccounts[string([]byte{0})].StorageUpdates["bar"] = &vmcommon.StorageUpdate{} + message = NewMessageContractResponse(vmOutput, nil) + requireSerializationConsistency(t, message, &MessageContractResponse{}) + + // Non UTF-8 as output account keys + vmOutput = &vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} + vmOutput.OutputAccounts[string([]byte{128})] = &vmcommon.OutputAccount{StorageUpdates: make(map[string]*vmcommon.StorageUpdate)} + vmOutput.OutputAccounts[string([]byte{128})].StorageUpdates["foo"] = &vmcommon.StorageUpdate{} + vmOutput.OutputAccounts[string([]byte{128})].StorageUpdates["bar"] = &vmcommon.StorageUpdate{} + message = NewMessageContractResponse(vmOutput, nil) + requireSerializationConsistency(t, message, &MessageContractResponse{}) + + // Non UTF-8 as storage update keys + vmOutput = &vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} + vmOutput.OutputAccounts["alice"] = &vmcommon.OutputAccount{StorageUpdates: make(map[string]*vmcommon.StorageUpdate)} + vmOutput.OutputAccounts["alice"].StorageUpdates[string([]byte{128})] = &vmcommon.StorageUpdate{} + vmOutput.OutputAccounts["alice"].StorageUpdates[string([]byte{129})] = &vmcommon.StorageUpdate{} + message = NewMessageContractResponse(vmOutput, nil) + requireSerializationConsistency(t, message, &MessageContractResponse{}) +} + +func TestMessageContractResponse_CanWrapNilVMOutput(t *testing.T) { + message := NewMessageContractResponse(nil, nil) + expectedEmptyVMOutput := vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} + actualVMOutput := *message.SerializableVMOutput.ConvertToVMOutput() + + require.Equal(t, spew.Sdump(expectedEmptyVMOutput), spew.Sdump(actualVMOutput)) + requireSerializationConsistency(t, message, &MessageContractResponse{}) +} + +func TestMessageBlockchainGetAllStateResponse_IsConsistentlySerializable(t *testing.T) { + t.Skip("GetAllState isn't used at this moment") + + allState := make(map[string][]byte) + allState["foo"] = []byte{0} + allState[string([]byte{0})] = []byte{0} + allState[string([]byte{128})] = []byte{0} + message := NewMessageBlockchainGetAllStateResponse(allState, nil) + requireSerializationConsistency(t, message, &MessageBlockchainGetAllStateResponse{}) +} + +func requireSerializationConsistency(t *testing.T, message interface{}, intoMessage interface{}) { + spew.Config.SortKeys = true + spew.Config.Indent = "\t" + marshalizer := marshaling.CreateMarshalizer(marshaling.JSON) + + serialized, err := marshalizer.Marshal(message) + require.Nil(t, err) + err = marshalizer.Unmarshal(intoMessage, serialized) + require.Nil(t, err) + + areEqual := reflect.DeepEqual(message, intoMessage) + if !areEqual { + fmt.Println("### Original message ###") + spew.Dump(message) + fmt.Println("### Into message ###") + spew.Dump(intoMessage) + require.FailNow(t, "Serialization is not consistent.") + } +} diff --git a/ipc/common/serializableVMOutput.go b/ipc/common/serializableVMOutput.go new file mode 100644 index 000000000..7c2965a56 --- /dev/null +++ b/ipc/common/serializableVMOutput.go @@ -0,0 +1,118 @@ +package common + +import ( + "math/big" + + vmcommon "github.com/ElrondNetwork/elrond-vm-common" +) + +type SerializableVMOutput struct { + ReturnData [][]byte + ReturnCode vmcommon.ReturnCode + ReturnMessage string + GasRemaining uint64 + GasRefund *big.Int + CorrectedOutputAccounts []*SerializableOutputAccount + DeletedAccounts [][]byte + TouchedAccounts [][]byte + Logs []*vmcommon.LogEntry +} + +func NewSerializableVMOutput(vmOutput *vmcommon.VMOutput) *SerializableVMOutput { + if vmOutput == nil { + return &SerializableVMOutput{} + } + + o := &SerializableVMOutput{ + ReturnData: vmOutput.ReturnData, + ReturnCode: vmOutput.ReturnCode, + ReturnMessage: vmOutput.ReturnMessage, + GasRemaining: vmOutput.GasRemaining, + GasRefund: vmOutput.GasRefund, + CorrectedOutputAccounts: make([]*SerializableOutputAccount, 0, len(vmOutput.OutputAccounts)), + DeletedAccounts: vmOutput.DeletedAccounts, + TouchedAccounts: vmOutput.TouchedAccounts, + Logs: vmOutput.Logs, + } + + for _, account := range vmOutput.OutputAccounts { + o.CorrectedOutputAccounts = append(o.CorrectedOutputAccounts, NewSerializableOutputAccount(account)) + } + + return o +} + +func (o *SerializableVMOutput) ConvertToVMOutput() *vmcommon.VMOutput { + accountsMap := make(map[string]*vmcommon.OutputAccount) + + for _, item := range o.CorrectedOutputAccounts { + accountsMap[string(item.Address)] = item.ConvertToOutputAccount() + } + + return &vmcommon.VMOutput{ + ReturnData: o.ReturnData, + ReturnCode: o.ReturnCode, + ReturnMessage: o.ReturnMessage, + GasRemaining: o.GasRemaining, + GasRefund: o.GasRefund, + OutputAccounts: accountsMap, + DeletedAccounts: o.DeletedAccounts, + TouchedAccounts: o.TouchedAccounts, + Logs: o.Logs, + } +} + +type SerializableOutputAccount struct { + Address []byte + Nonce uint64 + Balance *big.Int + BalanceDelta *big.Int + StorageUpdates []*vmcommon.StorageUpdate + Code []byte + CodeMetadata []byte + Data []byte + GasLimit uint64 + CallType vmcommon.CallType +} + +func NewSerializableOutputAccount(account *vmcommon.OutputAccount) *SerializableOutputAccount { + a := &SerializableOutputAccount{ + Address: account.Address, + Nonce: account.Nonce, + Balance: account.Balance, + BalanceDelta: account.BalanceDelta, + StorageUpdates: make([]*vmcommon.StorageUpdate, 0, len(account.StorageUpdates)), + Code: account.Code, + CodeMetadata: account.CodeMetadata, + Data: account.Data, + GasLimit: account.GasLimit, + CallType: account.CallType, + } + + for _, storageUpdate := range account.StorageUpdates { + a.StorageUpdates = append(a.StorageUpdates, storageUpdate) + } + + return a +} + +func (a *SerializableOutputAccount) ConvertToOutputAccount() *vmcommon.OutputAccount { + updatesMap := make(map[string]*vmcommon.StorageUpdate) + + for _, item := range a.StorageUpdates { + updatesMap[string(item.Offset)] = item + } + + return &vmcommon.OutputAccount{ + Address: a.Address, + Nonce: a.Nonce, + Balance: a.Balance, + BalanceDelta: a.BalanceDelta, + StorageUpdates: updatesMap, + Code: a.Code, + CodeMetadata: a.CodeMetadata, + Data: a.Data, + GasLimit: a.GasLimit, + CallType: a.CallType, + } +} diff --git a/ipc/nodepart/arwenDriver.go b/ipc/nodepart/arwenDriver.go index 79477a95f..29eb83446 100644 --- a/ipc/nodepart/arwenDriver.go +++ b/ipc/nodepart/arwenDriver.go @@ -214,7 +214,7 @@ func (driver *ArwenDriver) RunSmartContractCreate(input *vmcommon.ContractCreate } typedResponse := response.(*common.MessageContractResponse) - vmOutput, err := typedResponse.VMOutput, response.GetError() + vmOutput, err := typedResponse.SerializableVMOutput.ConvertToVMOutput(), response.GetError() if err != nil { return nil, err } @@ -241,7 +241,7 @@ func (driver *ArwenDriver) RunSmartContractCall(input *vmcommon.ContractCallInpu } typedResponse := response.(*common.MessageContractResponse) - vmOutput, err := typedResponse.VMOutput, response.GetError() + vmOutput, err := typedResponse.SerializableVMOutput.ConvertToVMOutput(), response.GetError() if err != nil { return nil, err } From 9813374a98f30bcb5b98eb26712048fb7b8c70e0 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 24 Jul 2020 16:53:27 +0300 Subject: [PATCH 2/3] Fix after review. --- go.mod | 1 - ipc/common/messages_test.go | 10 +--------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/go.mod b/go.mod index dcba56a4a..6f8bf1dd5 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/ElrondNetwork/elrond-go-logger v1.0.2 github.com/ElrondNetwork/elrond-vm-common v0.1.23 github.com/ElrondNetwork/elrond-vm-util v0.3.5 - github.com/davecgh/go-spew v1.1.1 github.com/gin-gonic/gin v1.6.3 github.com/mitchellh/mapstructure v1.1.2 github.com/pelletier/go-toml v1.6.0 diff --git a/ipc/common/messages_test.go b/ipc/common/messages_test.go index 9c392370a..f49876092 100644 --- a/ipc/common/messages_test.go +++ b/ipc/common/messages_test.go @@ -1,13 +1,11 @@ package common import ( - "fmt" "reflect" "testing" "github.com/ElrondNetwork/arwen-wasm-vm/ipc/marshaling" vmcommon "github.com/ElrondNetwork/elrond-vm-common" - "github.com/davecgh/go-spew/spew" "github.com/stretchr/testify/require" ) @@ -49,7 +47,7 @@ func TestMessageContractResponse_CanWrapNilVMOutput(t *testing.T) { expectedEmptyVMOutput := vmcommon.VMOutput{OutputAccounts: make(map[string]*vmcommon.OutputAccount)} actualVMOutput := *message.SerializableVMOutput.ConvertToVMOutput() - require.Equal(t, spew.Sdump(expectedEmptyVMOutput), spew.Sdump(actualVMOutput)) + require.True(t, reflect.DeepEqual(expectedEmptyVMOutput, actualVMOutput)) requireSerializationConsistency(t, message, &MessageContractResponse{}) } @@ -65,8 +63,6 @@ func TestMessageBlockchainGetAllStateResponse_IsConsistentlySerializable(t *test } func requireSerializationConsistency(t *testing.T, message interface{}, intoMessage interface{}) { - spew.Config.SortKeys = true - spew.Config.Indent = "\t" marshalizer := marshaling.CreateMarshalizer(marshaling.JSON) serialized, err := marshalizer.Marshal(message) @@ -76,10 +72,6 @@ func requireSerializationConsistency(t *testing.T, message interface{}, intoMess areEqual := reflect.DeepEqual(message, intoMessage) if !areEqual { - fmt.Println("### Original message ###") - spew.Dump(message) - fmt.Println("### Into message ###") - spew.Dump(intoMessage) require.FailNow(t, "Serialization is not consistent.") } } From e290fef99dcd74b4184ee064f940a5edd214ef2b Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 24 Jul 2020 19:24:18 +0300 Subject: [PATCH 3/3] Fix messages order (interval) - "isPayable" messages were out of interval. --- Makefile | 1 + ipc/common/messages.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a468a3a10..db09a6f60 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ build: arwen: go build -o ./cmd/arwen/arwen ./cmd/arwen cp ./cmd/arwen/arwen ./ipc/tests + cp ./cmd/arwen/arwen ${ARWEN_PATH} arwendebug: ifndef ARWENDEBUG_PATH diff --git a/ipc/common/messages.go b/ipc/common/messages.go index 81c80c932..782206f2f 100644 --- a/ipc/common/messages.go +++ b/ipc/common/messages.go @@ -53,10 +53,10 @@ const ( BlockchainGetUserAccountResponse BlockchainGetShardOfAddressRequest BlockchainGetShardOfAddressResponse - BlockchainIsSmartContractRequest - BlockchainIsSmartContractResponse BlockchainIsPayableRequest BlockchainIsPayableResponse + BlockchainIsSmartContractRequest + BlockchainIsSmartContractResponse DiagnoseWaitRequest DiagnoseWaitResponse UndefinedRequestOrResponse