From a647541dec18c1766e7c2cfc17051c173396f273 Mon Sep 17 00:00:00 2001 From: smblucker Date: Mon, 7 May 2018 13:02:06 -0400 Subject: [PATCH 1/4] Implementing INVALID opcode Signed-off-by: smblucker --- execution/evm/asm/opcodes.go | 2 ++ execution/evm/vm.go | 8 ++++++-- execution/evm/vm_test.go | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/execution/evm/asm/opcodes.go b/execution/evm/asm/opcodes.go index 0db53834f..8dd393402 100644 --- a/execution/evm/asm/opcodes.go +++ b/execution/evm/asm/opcodes.go @@ -189,6 +189,7 @@ const ( // 0x70 range - other STATICCALL = 0xfa REVERT = 0xfd + INVALID = 0xfe SELFDESTRUCT = 0xff ) @@ -347,6 +348,7 @@ var opCodeNames = map[OpCode]string{ STATICCALL: "STATICCALL", // 0x70 range - other REVERT: "REVERT", + INVALID: "INVALID", SELFDESTRUCT: "SELFDESTRUCT", } diff --git a/execution/evm/vm.go b/execution/evm/vm.go index 5597fe037..35a3ac63a 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -48,6 +48,7 @@ var ( ErrDataStackUnderflow = errors.New("Data stack underflow") ErrInvalidContract = errors.New("Invalid contract") ErrNativeContractCodeCopy = errors.New("Tried to copy native contract code") + ErrExecutionAborted = errors.New("Execution aborted") ErrExecutionReverted = errors.New("Execution reverted") ) @@ -1039,6 +1040,9 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, ErrExecutionReverted + case INVALID: //0xFE + return nil, ErrExecutionAborted + case SELFDESTRUCT: // 0xFF addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { @@ -1079,8 +1083,8 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] case STATICCALL, SHL, SHR, SAR, RETURNDATASIZE, RETURNDATACOPY: return nil, fmt.Errorf("%s not yet implemented", op.Name()) default: - vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op) - return nil, fmt.Errorf("invalid opcode %X", op) + vm.Debugf("(pc) %-3v Unknown opcode %X\n", pc, op) + return nil, fmt.Errorf("unknown opcode %X", op) } pc++ } diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go index 27ce07708..23f942a8c 100644 --- a/execution/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -374,6 +374,26 @@ func TestMsgSender(t *testing.T) { } +func TestInvalid(t *testing.T) { + ourVm := NewVM(newAppState(), newParams(), acm.ZeroAddress, nil, logger) + + // Create accounts + account1 := newAccount(1) + account2 := newAccount(1, 0, 1) + + var gas uint64 = 100000 + + bytecode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, INVALID) + + output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas) + expected := "call error: " + ErrExecutionAborted.Error() + assert.EqualError(t, err, expected) + t.Logf("Output: %v Error: %v\n", output, err) + +} + // These code segment helpers exercise the MSTORE MLOAD MSTORE cycle to test // both of the memory operations. Each MSTORE is done on the memory boundary // (at MSIZE) which Solidity uses to find guaranteed unallocated memory. From 3f47c398dbf491e08f84cfcf6f69ef9d014af7c3 Mon Sep 17 00:00:00 2001 From: Silas Davis Date: Thu, 10 May 2018 17:43:48 +0100 Subject: [PATCH 2/4] Introduce InputAccount on param to simplify JS Signed-off-by: Silas Davis --- project/history.go | 4 +++ rpc/v0/integration/v0_test.go | 59 ++++++++++++----------------------- rpc/v0/methods.go | 26 +++++++-------- rpc/v0/params.go | 50 +++++++++++++++-------------- 4 files changed, 63 insertions(+), 76 deletions(-) diff --git a/project/history.go b/project/history.go index 9d82ee54f..f740a0a6f 100644 --- a/project/history.go +++ b/project/history.go @@ -28,6 +28,10 @@ func FullVersion() string { // To cut a new release add a release to the front of this slice then run the // release tagging script: ./scripts/tag_release.sh var History relic.ImmutableHistory = relic.NewHistory("Hyperledger Burrow").MustDeclareReleases( + "0.18.1", + `This is a minor release including: +- Introduce InputAccount param for RPC/v0 for integration in JS libs +- Resolve some issues with RPC/tm tests swallowing timeouts and not dealing with reordered events`, "0.18.0", `This is an extremely large release in terms of lines of code changed addressing several years of technical debt. Despite this efforts were made to maintain external interfaces as much as possible and an extended period of stabilisation has taken place on develop. diff --git a/rpc/v0/integration/v0_test.go b/rpc/v0/integration/v0_test.go index 8c333c740..55590ff15 100644 --- a/rpc/v0/integration/v0_test.go +++ b/rpc/v0/integration/v0_test.go @@ -18,15 +18,12 @@ package integration import ( + "context" "encoding/hex" "fmt" - "testing" - - "context" - "sync" + "testing" - "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/binary" "github.com/hyperledger/burrow/consensus/tendermint" "github.com/hyperledger/burrow/execution/evm/abi" @@ -40,15 +37,13 @@ func TestTransactCallNoCode(t *testing.T) { cli := v0.NewV0Client("http://localhost:1337/rpc") // Flip flops between sending private key and input address to test private key and address based signing - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) toAddress := privateAccounts[2].Address() numCreates := 1000 countCh := committedTxCount(t) for i := 0; i < numCreates; i++ { receipt, err := cli.Transact(v0.TransactParam{ - PrivKey: privKey(i), - InputAddress: inputAddress(i), + InputAccount: inputAccount(i), Address: toAddress.Bytes(), Data: []byte{}, Fee: 2, @@ -68,7 +63,6 @@ func TestTransactCreate(t *testing.T) { wg.Add(numGoroutines) cli := v0.NewV0Client("http://localhost:1337/rpc") // Flip flops between sending private key and input address to test private key and address based signing - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) bc, err := hex.DecodeString(strangeLoopBytecode) require.NoError(t, err) countCh := committedTxCount(t) @@ -76,8 +70,7 @@ func TestTransactCreate(t *testing.T) { go func() { for j := 0; j < numCreates; j++ { create, err := cli.Transact(v0.TransactParam{ - PrivKey: privKey(j), - InputAddress: inputAddress(j), + InputAccount: inputAccount(i), Address: nil, Data: bc, Fee: 2, @@ -98,13 +91,11 @@ func TestTransactCreate(t *testing.T) { func BenchmarkTransactCreateContract(b *testing.B) { cli := v0.NewV0Client("http://localhost:1337/rpc") - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) bc, err := hex.DecodeString(strangeLoopBytecode) require.NoError(b, err) for i := 0; i < b.N; i++ { create, err := cli.Transact(v0.TransactParam{ - PrivKey: privKey(i), - InputAddress: inputAddress(i), + InputAccount: inputAccount(i), Address: nil, Data: bc, Fee: 2, @@ -119,7 +110,6 @@ func TestTransactAndHold(t *testing.T) { cli := v0.NewV0Client("http://localhost:1337/rpc") bc, err := hex.DecodeString(strangeLoopBytecode) require.NoError(t, err) - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) numGoroutines := 5 numRuns := 2 @@ -127,8 +117,7 @@ func TestTransactAndHold(t *testing.T) { for i := 0; i < numGoroutines; i++ { for j := 0; j < numRuns; j++ { create, err := cli.TransactAndHold(v0.TransactParam{ - PrivKey: privKey(j), - InputAddress: inputAddress(j), + InputAccount: inputAccount(i), Address: nil, Data: bc, Fee: 2, @@ -138,8 +127,7 @@ func TestTransactAndHold(t *testing.T) { assert.Equal(t, 0, create.StackDepth) functionID := abi.FunctionID("UpsieDownsie()") call, err := cli.TransactAndHold(v0.TransactParam{ - PrivKey: privKey(j), - InputAddress: inputAddress(j), + InputAccount: inputAccount(i), Address: create.CallData.Callee.Bytes(), Data: functionID[:], Fee: 2, @@ -158,12 +146,10 @@ func TestSend(t *testing.T) { cli := v0.NewV0Client("http://localhost:1337/rpc") numSends := 1000 - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) countCh := committedTxCount(t) for i := 0; i < numSends; i++ { send, err := cli.Send(v0.SendParam{ - PrivKey: privKey(i), - InputAddress: inputAddress(i), + InputAccount: inputAccount(i), Amount: 2003, ToAddress: privateAccounts[3].Address().Bytes(), }) @@ -176,12 +162,9 @@ func TestSend(t *testing.T) { func TestSendAndHold(t *testing.T) { cli := v0.NewV0Client("http://localhost:1337/rpc") - privKey, inputAddress := privKeyInputAddressAlternator(privateAccounts[0]) - for i := 0; i < 2; i++ { send, err := cli.SendAndHold(v0.SendParam{ - PrivKey: privKey(i), - InputAddress: inputAddress(i), + InputAccount: inputAccount(i), Amount: 2003, ToAddress: privateAccounts[3].Address().Bytes(), }) @@ -190,6 +173,8 @@ func TestSendAndHold(t *testing.T) { } } +// Helpers + var committedTxCountIndex = 0 func committedTxCount(t *testing.T) chan int { @@ -222,19 +207,15 @@ func committedTxCount(t *testing.T) chan int { return outCh } -// Returns a pair of functions that mutually exclusively return the private key bytes or input address bytes of a -// private account in the same iteration of a loop indexed by an int -func privKeyInputAddressAlternator(privateAccount account.PrivateAccount) (func(int) []byte, func(int) []byte) { - privKey := privateAccount.PrivateKey().RawBytes() - inputAddress := privateAccount.Address().Bytes() - return alternator(privKey, 0), alternator(inputAddress, 1) -} +var inputPrivateKey = privateAccounts[0].PrivateKey().RawBytes() +var inputAddress = privateAccounts[0].Address().Bytes() -func alternator(ret []byte, res int) func(int) []byte { - return func(i int) []byte { - if i%2 == res { - return ret - } - return nil +func inputAccount(i int) v0.InputAccount { + ia := v0.InputAccount{} + if i%2 == 0 { + ia.PrivateKey = inputPrivateKey + } else { + ia.Address = inputAddress } + return ia } diff --git a/rpc/v0/methods.go b/rpc/v0/methods.go index 346b53148..0226189e3 100644 --- a/rpc/v0/methods.go +++ b/rpc/v0/methods.go @@ -147,12 +147,12 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m return acm.AsConcretePrivateAccount(pa), 0, nil }, GEN_PRIV_ACCOUNT_FROM_KEY: func(request *rpc.RPCRequest, requester interface{}) (interface{}, int, error) { - param := &PrivKeyParam{} + param := &PrivateKeyParam{} err := codec.DecodeBytes(param, request.Params) if err != nil { return nil, rpc.INVALID_PARAMS, err } - pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(param.PrivKey) + pa, err := acm.GeneratePrivateAccountFromPrivateKeyBytes(param.PrivateKey) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -214,7 +214,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - txRet, err := service.Transactor().SignTx(param.Tx, acm.SigningAccounts(param.PrivAccounts)) + txRet, err := service.Transactor().SignTx(param.Tx, acm.SigningAccounts(param.PrivateAccounts)) if err != nil { return nil, rpc.INTERNAL_ERROR, err } @@ -231,7 +231,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m return nil, rpc.INVALID_PARAMS, err } // Use mempool state so that transact can generate a run of sequence numbers when formulating transactions - inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.InputAccount) if err != nil { return nil, rpc.INVALID_PARAMS, err } @@ -251,7 +251,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.InputAccount) if err != nil { return nil, rpc.INVALID_PARAMS, err } @@ -272,7 +272,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m return nil, rpc.INVALID_PARAMS, err } // Run Send against mempool state - inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.InputAccount) if err != nil { return nil, rpc.INVALID_PARAMS, err } @@ -293,7 +293,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m return nil, rpc.INVALID_PARAMS, err } // Run Send against mempool state - inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.InputAccount) if err != nil { return nil, rpc.INVALID_PARAMS, err } @@ -309,7 +309,7 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m if err != nil { return nil, rpc.INVALID_PARAMS, err } - inputAccount, err := signingAccount(service.MempoolAccounts(), param.PrivKey, param.InputAddress) + inputAccount, err := signingAccount(service.MempoolAccounts(), param.InputAccount) if err != nil { return nil, rpc.INVALID_PARAMS, err } @@ -444,17 +444,17 @@ func GetMethods(codec rpc.Codec, service *rpc.Service, logger *logging.Logger) m } // Gets signing account from onr of private key or address - failing if both are provided -func signingAccount(accounts *execution.Accounts, privKey, addressBytes []byte) (*execution.SequentialSigningAccount, error) { - if len(addressBytes) > 0 { - if len(privKey) > 0 { +func signingAccount(accounts *execution.Accounts, inputAccount InputAccount) (*execution.SequentialSigningAccount, error) { + if len(inputAccount.Address) > 0 { + if len(inputAccount.PrivateKey) > 0 { return nil, fmt.Errorf("privKey and address provided but only one or the other should be given") } - address, err := acm.AddressFromBytes(addressBytes) + address, err := acm.AddressFromBytes(inputAccount.Address) if err != nil { return nil, err } return accounts.SequentialSigningAccount(address), nil } - return accounts.SequentialSigningAccountFromPrivateKey(privKey) + return accounts.SequentialSigningAccountFromPrivateKey(inputAccount.PrivateKey) } diff --git a/rpc/v0/params.go b/rpc/v0/params.go index 2635308ef..a93f9a06e 100644 --- a/rpc/v0/params.go +++ b/rpc/v0/params.go @@ -31,8 +31,13 @@ type ( Filters []*filters.FilterData `json:"filters"` } - PrivKeyParam struct { - PrivKey []byte `json:"priv_key"` + PrivateKeyParam struct { + PrivateKey []byte `json:"privateKey"` + } + + InputAccount struct { + PrivateKey []byte `json:"privateKey"` + Address []byte `json:"address"` } // StorageAt @@ -47,18 +52,18 @@ type ( } BlocksParam struct { - MinHeight uint64 `json:"min_height"` - MaxHeight uint64 `json:"max_height"` + MinHeight uint64 `json:"minHeight"` + MaxHeight uint64 `json:"maxHeight"` } // Event Id EventIdParam struct { - EventId string `json:"event_id"` + EventId string `json:"eventId"` } // Event Id SubIdParam struct { - SubId string `json:"sub_id"` + SubId string `json:"subId"` } PeerParam struct { @@ -81,27 +86,25 @@ type ( // Used when signing a tx. Uses placeholders just like TxParam SignTxParam struct { - Tx *txs.CallTx `json:"tx"` - PrivAccounts []*acm.ConcretePrivateAccount `json:"priv_accounts"` + Tx *txs.CallTx `json:"tx"` + PrivateAccounts []*acm.ConcretePrivateAccount `json:"privateAccounts"` } // Used when sending a transaction to be created and signed on the server // (using the private key). This only uses the standard key type for now. TransactParam struct { - PrivKey []byte `json:"priv_key"` - InputAddress []byte `json:"input_account"` - Data []byte `json:"data"` - Address []byte `json:"address"` - Fee uint64 `json:"fee"` - GasLimit uint64 `json:"gas_limit"` + InputAccount InputAccount `json:"inputAccount"` + Data []byte `json:"data"` + Address []byte `json:"address"` + Fee uint64 `json:"fee"` + GasLimit uint64 `json:"gasLimit"` } // Used when sending a 'Send' transaction. SendParam struct { - PrivKey []byte `json:"priv_key"` - InputAddress []byte `json:"input_account"` - ToAddress []byte `json:"to_address"` - Amount uint64 `json:"amount"` + InputAccount InputAccount `json:"inputAccount"` + ToAddress []byte `json:"toAddress"` + Amount uint64 `json:"amount"` } NameRegEntryParam struct { @@ -111,11 +114,10 @@ type ( // Used when sending a namereg transaction to be created and signed on the server // (using the private key). This only uses the standard key type for now. TransactNameRegParam struct { - PrivKey []byte `json:"priv_key"` - InputAddress []byte `json:"input_account"` - Name string `json:"name"` - Data string `json:"data"` - Fee uint64 `json:"fee"` - Amount uint64 `json:"amount"` + InputAccount InputAccount `json:"inputAccount"` + Name string `json:"name"` + Data string `json:"data"` + Fee uint64 `json:"fee"` + Amount uint64 `json:"amount"` } ) From 94669ce5639bc02bbc498c535531e32899a3b7d3 Mon Sep 17 00:00:00 2001 From: Silas Davis Date: Fri, 11 May 2018 13:11:35 +0100 Subject: [PATCH 3/4] Cleanup some subscription code on V0 Increase reap threshold to 20 seconds Signed-off-by: Silas Davis --- rpc/v0/json_service.go | 2 +- rpc/v0/json_service_test.go | 15 ++++++++ rpc/v0/subscriptions.go | 67 ++++++++++++++++++------------------ rpc/v0/subscriptions_test.go | 8 ++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/rpc/v0/json_service.go b/rpc/v0/json_service.go index d075eeddc..08459b704 100644 --- a/rpc/v0/json_service.go +++ b/rpc/v0/json_service.go @@ -27,7 +27,7 @@ import ( // EventSubscribe type EventSub struct { - SubId string `json:"sub_id"` + SubId string `json:"subId"` } // EventUnsubscribe diff --git a/rpc/v0/json_service_test.go b/rpc/v0/json_service_test.go index 86dbad4cf..23ec76209 100644 --- a/rpc/v0/json_service_test.go +++ b/rpc/v0/json_service_test.go @@ -13,3 +13,18 @@ // limitations under the License. package v0 + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPollResponse(t *testing.T) { + pr := PollResponse{} + bs, err := json.Marshal(pr) + require.NoError(t, err) + fmt.Println(string(bs)) +} diff --git a/rpc/v0/subscriptions.go b/rpc/v0/subscriptions.go index 8bb918647..74fff345a 100644 --- a/rpc/v0/subscriptions.go +++ b/rpc/v0/subscriptions.go @@ -25,10 +25,18 @@ import ( ) var ( - reaperTimeout = 5 * time.Second - reaperThreshold = 10 * time.Second + reaperPeriod = 5 * time.Second + reaperThreshold = 20 * time.Second ) +// Catches events that callers subscribe to and adds them to an array ready to be polled. +type Subscriptions struct { + mtx *sync.RWMutex + service *rpc.Service + subs map[string]*SubscriptionsCache + reap bool +} + type SubscriptionsCache struct { mtx *sync.Mutex events []interface{} @@ -36,6 +44,19 @@ type SubscriptionsCache struct { subId string } +func NewSubscriptions(service *rpc.Service) *Subscriptions { + es := &Subscriptions{ + mtx: &sync.RWMutex{}, + service: service, + subs: make(map[string]*SubscriptionsCache), + reap: true, + } + if es.reap { + go reap(es) + } + return es +} + func newSubscriptionsCache() *SubscriptionsCache { return &SubscriptionsCache{ &sync.Mutex{}, @@ -59,40 +80,19 @@ func (subsCache *SubscriptionsCache) poll() []interface{} { return evts } -// Catches events that callers subscribe to and adds them to an array ready to be polled. -type Subscriptions struct { - mtx *sync.RWMutex - service *rpc.Service - subs map[string]*SubscriptionsCache - reap bool -} - -func NewSubscriptions(service *rpc.Service) *Subscriptions { - es := &Subscriptions{ - mtx: &sync.RWMutex{}, - service: service, - subs: make(map[string]*SubscriptionsCache), - reap: true, - } - go reap(es) - return es -} - +// Remove old subscriptions not recently polled func reap(es *Subscriptions) { - if !es.reap { - return - } - time.Sleep(reaperTimeout) - es.mtx.Lock() - defer es.mtx.Unlock() - for id, sub := range es.subs { - if time.Since(sub.ts) > reaperThreshold { - // Seems like Go is ok with this.. - delete(es.subs, id) - es.service.Unsubscribe(context.Background(), id) + for { + time.Sleep(reaperPeriod) + for id, sub := range es.subs { + if time.Since(sub.ts) > reaperThreshold { + es.mtx.Lock() + delete(es.subs, id) + es.service.Unsubscribe(context.Background(), id) + es.mtx.Unlock() + } } } - go reap(es) } // Add a subscription and return the generated id. Note event dispatcher @@ -136,7 +136,6 @@ func (subs *Subscriptions) Poll(subId string) ([]interface{}, error) { func (subs *Subscriptions) Remove(subId string) error { subs.mtx.Lock() defer subs.mtx.Unlock() - // TODO Check this. _, ok := subs.subs[subId] if !ok { return fmt.Errorf("Subscription not active. ID: " + subId) diff --git a/rpc/v0/subscriptions_test.go b/rpc/v0/subscriptions_test.go index c3032dd6f..96be266f7 100644 --- a/rpc/v0/subscriptions_test.go +++ b/rpc/v0/subscriptions_test.go @@ -17,7 +17,6 @@ package v0 import ( "encoding/hex" "fmt" - "runtime" "testing" "time" @@ -31,10 +30,9 @@ var mockInterval = 20 * time.Millisecond // Test that event subscriptions can be added manually and then automatically reaped. func TestSubReaping(t *testing.T) { - runtime.GOMAXPROCS(runtime.NumCPU()) NUM_SUBS := 100 reaperThreshold = 200 * time.Millisecond - reaperTimeout = 100 * time.Millisecond + reaperPeriod = 100 * time.Millisecond mee := event.NewEmitter(logging.NewNoopLogger()) eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, logging.NewNoopLogger())) @@ -78,7 +76,7 @@ func TestSubManualClose(t *testing.T) { NUM_SUBS := 100 // Keep the reaper out of this. reaperThreshold = 10000 * time.Millisecond - reaperTimeout = 10000 * time.Millisecond + reaperPeriod = 10000 * time.Millisecond mee := event.NewEmitter(logging.NewNoopLogger()) eSubs := NewSubscriptions(rpc.NewSubscribableService(mee, logging.NewNoopLogger())) @@ -125,7 +123,7 @@ func TestSubFlooding(t *testing.T) { NUM_SUBS := 100 // Keep the reaper out of this. reaperThreshold = 10000 * time.Millisecond - reaperTimeout = 10000 * time.Millisecond + reaperPeriod = 10000 * time.Millisecond // Crank it up. Now pressure is 10 times higher on each sub. mockInterval = 1 * time.Millisecond mee := event.NewEmitter(logging.NewNoopLogger()) From bb55ebe3de63a92f2a6b27531b107b8ad07fe6e0 Mon Sep 17 00:00:00 2001 From: smblucker Date: Fri, 11 May 2018 13:41:04 -0400 Subject: [PATCH 4/4] Implementing returndatasize and returndatacopy Signed-off-by: smblucker --- execution/evm/vm.go | 49 ++++++++++++++++++++++++- execution/evm/vm_test.go | 77 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/execution/evm/vm.go b/execution/evm/vm.go index 35a3ac63a..27a23d702 100644 --- a/execution/evm/vm.go +++ b/execution/evm/vm.go @@ -42,6 +42,7 @@ var ( ErrMemoryOutOfBounds = errors.New("Memory out of bounds") ErrCodeOutOfBounds = errors.New("Code out of bounds") ErrInputOutOfBounds = errors.New("Input out of bounds") + ErrReturnDataOutOfBounds = errors.New("Return data out of bounds") ErrCallStackOverflow = errors.New("Call stack overflow") ErrCallStackUnderflow = errors.New("Call stack underflow") ErrDataStackOverflow = errors.New("Data stack overflow") @@ -113,6 +114,7 @@ type VM struct { nestedCallErrors []ErrNestedCall publisher event.Publisher logger *logging.Logger + returnData []byte debugOpcodes bool dumpTokens bool } @@ -145,6 +147,14 @@ func (vm *VM) SetPublisher(publisher event.Publisher) { vm.publisher = publisher } +func (vm *VM) setReturnDataBuffer(ret []byte) { + vm.returnData = ret +} + +func (vm *VM) getReturnDataBuffer() (ret []byte) { + return vm.returnData +} + // CONTRACT: it is the duty of the contract writer to call known permissions // we do not convey if a permission is not set // (unlike in state/execution, where we guarantee HasPermission is called @@ -666,6 +676,38 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] } vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + case RETURNDATASIZE: // 0x3D + stack.Push64(int64(len(vm.getReturnDataBuffer()))) + vm.Debugf(" => %d\n", len(vm.getReturnDataBuffer())) + + case RETURNDATACOPY: // 0x3E + memOff := stack.PopBigInt() + outputOff, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + length, popErr := stack.Pop64() + if popErr != nil { + return nil, firstErr(err, popErr) + } + + bigOff := big.NewInt(outputOff) + bigLength := big.NewInt(length) + end := new(big.Int).Add(bigOff, bigLength) + + if end.BitLen() > 64 || uint64(len(vm.getReturnDataBuffer())) < end.Uint64() { + return nil, ErrReturnDataOutOfBounds + } + + data := vm.getReturnDataBuffer() + + memErr := memory.Write(memOff, data) + if memErr != nil { + vm.Debugf(" => Memory err: %s", memErr) + return nil, firstErr(err, ErrMemoryOutOfBounds) + } + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, outputOff, length, data) + case BLOCKHASH: // 0x40 stack.Push(Zero256) vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) @@ -871,6 +913,8 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] } else { newAccount.SetCode(ret) // Set the code (ret need not be copied as per Call contract) stack.Push(newAccount.Address().Word256()) + emptyBuffer := []byte{} + vm.setReturnDataBuffer(emptyBuffer) } if err_ == ErrExecutionReverted { @@ -955,11 +999,13 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] return nil, firstErr(callErr, ErrUnknownAddress) } ret, callErr = vm.Call(callee, callee, acc.Code(), args, value, &gasLimit) + vm.setReturnDataBuffer(ret) } else if op == DELEGATECALL { if acc == nil { return nil, firstErr(callErr, ErrUnknownAddress) } ret, callErr = vm.DelegateCall(caller, callee, acc.Code(), args, value, &gasLimit) + vm.setReturnDataBuffer(ret) } else { // nil account means we're sending funds to a new account if acc == nil { @@ -971,6 +1017,7 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] // add account to the tx cache vm.stateWriter.UpdateAccount(acc) ret, callErr = vm.Call(callee, acc, acc.Code(), args, value, &gasLimit) + vm.setReturnDataBuffer(ret) } } // In case any calls deeper in the stack (particularly SNatives) has altered either of two accounts to which @@ -1080,7 +1127,7 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input [] case STOP: // 0x00 return nil, nil - case STATICCALL, SHL, SHR, SAR, RETURNDATASIZE, RETURNDATACOPY: + case STATICCALL, SHL, SHR, SAR: return nil, fmt.Errorf("%s not yet implemented", op.Name()) default: vm.Debugf("(pc) %-3v Unknown opcode %X\n", pc, op) diff --git a/execution/evm/vm_test.go b/execution/evm/vm_test.go index 23f942a8c..ffa5454c8 100644 --- a/execution/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -394,6 +394,83 @@ func TestInvalid(t *testing.T) { } +func TestReturnDataSize(t *testing.T) { + cache := state.NewCache(newAppState()) + ourVm := NewVM(cache, newParams(), acm.ZeroAddress, nil, logger) + + accountName := "account2addresstests" + + callcode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, RETURN) + + // Create accounts + account1 := newAccount(1) + account2, _ := makeAccountWithCode(cache, accountName, callcode) + + var gas uint64 = 100000 + + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x69) + inOff, inSize := byte(0x0), byte(0x0) // no call data + retOff, retSize := byte(0x0), byte(0x0E) + + bytecode := MustSplice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, + 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x32, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x74, 0x65, + 0x73, 0x74, 0x73, PUSH2, gas1, gas2, CALL, RETURNDATASIZE, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN) + + expected := LeftPadBytes([]byte{0x0E}, 32) + + output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas) + + assert.Equal(t, expected, output) + + t.Logf("Output: %v Error: %v\n", output, err) + + if err != nil { + t.Fatal(err) + } +} + +func TestReturnDataCopy(t *testing.T) { + cache := state.NewCache(newAppState()) + ourVm := NewVM(cache, newParams(), acm.ZeroAddress, nil, logger) + + accountName := "account2addresstests" + + callcode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, RETURN) + + // Create accounts + account1 := newAccount(1) + account2, _ := makeAccountWithCode(cache, accountName, callcode) + + var gas uint64 = 100000 + + gas1, gas2 := byte(0x1), byte(0x1) + value := byte(0x69) + inOff, inSize := byte(0x0), byte(0x0) // no call data + retOff, retSize := byte(0x0), byte(0x0E) + + bytecode := MustSplice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, + 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x32, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x74, 0x65, + 0x73, 0x74, 0x73, PUSH2, gas1, gas2, CALL, RETURNDATASIZE, PUSH1, 0x00, PUSH1, 0x00, RETURNDATACOPY, + RETURNDATASIZE, PUSH1, 0x00, RETURN) + + expected := []byte{0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65} + + output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas) + + assert.Equal(t, expected, output) + + t.Logf("Output: %v Error: %v\n", output, err) + + if err != nil { + t.Fatal(err) + } +} + // These code segment helpers exercise the MSTORE MLOAD MSTORE cycle to test // both of the memory operations. Each MSTORE is done on the memory boundary // (at MSIZE) which Solidity uses to find guaranteed unallocated memory.