Skip to content

Commit

Permalink
Merge pull request #151 from ElrondNetwork/fix-json-vmoutput
Browse files Browse the repository at this point in the history
Wrap VMOutput in a JSON-friendly structure
  • Loading branch information
andreibancioiu authored Jul 24, 2020
2 parents 7be631c + e290fef commit 87a891f
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 6 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ipc/common/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ const (
BlockchainGetUserAccountResponse
BlockchainGetShardOfAddressRequest
BlockchainGetShardOfAddressResponse
BlockchainIsSmartContractRequest
BlockchainIsSmartContractResponse
BlockchainIsPayableRequest
BlockchainIsPayableResponse
BlockchainIsSmartContractRequest
BlockchainIsSmartContractResponse
DiagnoseWaitRequest
DiagnoseWaitResponse
UndefinedRequestOrResponse
Expand Down
1 change: 1 addition & 0 deletions ipc/common/messagesBlockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
4 changes: 2 additions & 2 deletions ipc/common/messagesContracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
77 changes: 77 additions & 0 deletions ipc/common/messages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package common

import (
"reflect"
"testing"

"github.com/ElrondNetwork/arwen-wasm-vm/ipc/marshaling"
vmcommon "github.com/ElrondNetwork/elrond-vm-common"
"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.True(t, reflect.DeepEqual(expectedEmptyVMOutput, 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{}) {
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 {
require.FailNow(t, "Serialization is not consistent.")
}
}
118 changes: 118 additions & 0 deletions ipc/common/serializableVMOutput.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
4 changes: 2 additions & 2 deletions ipc/nodepart/arwenDriver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down

0 comments on commit 87a891f

Please sign in to comment.