forked from TrueCloudLab/neoneo-go
parent
7d46404e2d
commit
ff4384d7ff
11 changed files with 344 additions and 2 deletions
|
@ -36,7 +36,7 @@ which would yield the response:
|
|||
| Method | Implemented |
|
||||
| ------- | ------------|
|
||||
| `getaccountstate` | Yes |
|
||||
| `getapplicationlog` | No (#500) |
|
||||
| `getapplicationlog` | Yes |
|
||||
| `getassetstate` | Yes |
|
||||
| `getbestblockhash` | Yes |
|
||||
| `getblock` | Yes |
|
||||
|
|
|
@ -839,6 +839,12 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
|
|||
return bc.dao.GetTransaction(hash)
|
||||
}
|
||||
|
||||
// GetAppExecResult returns application execution result by the given
|
||||
// tx hash.
|
||||
func (bc *Blockchain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
||||
return bc.dao.GetAppExecResult(hash)
|
||||
}
|
||||
|
||||
// GetStorageItem returns an item from storage.
|
||||
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
||||
return bc.dao.GetStorageItem(scripthash, key)
|
||||
|
|
|
@ -32,6 +32,7 @@ type Blockchainer interface {
|
|||
HasTransaction(util.Uint256) bool
|
||||
GetAssetState(util.Uint256) *state.Asset
|
||||
GetAccountState(util.Uint160) *state.Account
|
||||
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||
|
|
|
@ -67,6 +67,9 @@ func (chain *testChain) Close() {
|
|||
func (chain testChain) HeaderHeight() uint32 {
|
||||
return 0
|
||||
}
|
||||
func (chain testChain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
|
60
pkg/rpc/response/result/application_log.go
Normal file
60
pkg/rpc/response/result/application_log.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package result
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// ApplicationLog wrapper used for the representation of the
|
||||
// state.AppExecResult based on the specific tx on the RPC Server.
|
||||
type ApplicationLog struct {
|
||||
TxHash util.Uint256 `json:"txid"`
|
||||
Executions []Execution `json:"executions"`
|
||||
}
|
||||
|
||||
// Execution response wrapper
|
||||
type Execution struct {
|
||||
Trigger string `json:"trigger"`
|
||||
ScriptHash util.Uint160 `json:"contract"`
|
||||
VMState string `json:"vmstate"`
|
||||
GasConsumed util.Fixed8 `json:"gas_consumed"`
|
||||
Stack json.RawMessage `json:"stack"`
|
||||
Events []NotificationEvent `json:"notifications"`
|
||||
}
|
||||
|
||||
//NotificationEvent response wrapper
|
||||
type NotificationEvent struct {
|
||||
Contract util.Uint160 `json:"contract"`
|
||||
Item smartcontract.Parameter `json:"state"`
|
||||
}
|
||||
|
||||
// NewApplicationLog creates a new ApplicationLog wrapper.
|
||||
func NewApplicationLog(appExecRes *state.AppExecResult, scriptHash util.Uint160) ApplicationLog {
|
||||
events := make([]NotificationEvent, 0, len(appExecRes.Events))
|
||||
for _, e := range appExecRes.Events {
|
||||
item := e.Item.ToContractParameter()
|
||||
events = append(events, NotificationEvent{
|
||||
Contract: e.ScriptHash,
|
||||
Item: item,
|
||||
})
|
||||
}
|
||||
|
||||
triggerString := appExecRes.Trigger.String()
|
||||
|
||||
executions := []Execution{{
|
||||
Trigger: triggerString,
|
||||
ScriptHash: scriptHash,
|
||||
VMState: appExecRes.VMState,
|
||||
GasConsumed: appExecRes.GasConsumed,
|
||||
Stack: json.RawMessage(appExecRes.Stack),
|
||||
Events: events,
|
||||
}}
|
||||
|
||||
return ApplicationLog{
|
||||
TxHash: appExecRes.TxHash,
|
||||
Executions: executions,
|
||||
}
|
||||
}
|
|
@ -4,6 +4,13 @@ import "github.com/prometheus/client_golang/prometheus"
|
|||
|
||||
// Metrics used in monitoring service.
|
||||
var (
|
||||
getapplicationlogCalled = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Help: "Number of calls to getapplicationlog rpc endpoint",
|
||||
Name: "getapplicationlog_called",
|
||||
Namespace: "neogo",
|
||||
},
|
||||
)
|
||||
getbestblockhashCalled = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Help: "Number of calls to getbestblockhash rpc endpoint",
|
||||
|
@ -143,6 +150,7 @@ var (
|
|||
|
||||
func init() {
|
||||
prometheus.MustRegister(
|
||||
getapplicationlogCalled,
|
||||
getbestblockhashCalled,
|
||||
getbestblockCalled,
|
||||
getblockcountCalled,
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/CityOfZion/neo-go/pkg/core"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/network"
|
||||
|
@ -114,6 +115,10 @@ func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams
|
|||
|
||||
Methods:
|
||||
switch req.Method {
|
||||
case "getapplicationlog":
|
||||
getapplicationlogCalled.Inc()
|
||||
results, resultsErr = s.getApplicationLog(reqParams)
|
||||
|
||||
case "getbestblockhash":
|
||||
getbestblockhashCalled.Inc()
|
||||
results = "0x" + s.chain.CurrentBlockHash().StringLE()
|
||||
|
@ -284,6 +289,39 @@ Methods:
|
|||
s.WriteResponse(req, w, results)
|
||||
}
|
||||
|
||||
// getApplicationLog returns the contract log based on the specified txid.
|
||||
func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, error) {
|
||||
param, ok := reqParams.Value(0)
|
||||
if !ok {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
txHash, err := param.GetUint256()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
appExecResult, err := s.chain.GetAppExecResult(txHash)
|
||||
if err != nil {
|
||||
return nil, response.NewRPCError("Unknown transaction", "", nil)
|
||||
}
|
||||
|
||||
tx, _, err := s.chain.GetTransaction(txHash)
|
||||
if err != nil {
|
||||
return nil, response.NewRPCError("Error while getting transaction", "", nil)
|
||||
}
|
||||
|
||||
var scriptHash util.Uint160
|
||||
switch t := tx.Data.(type) {
|
||||
case *transaction.InvocationTX:
|
||||
scriptHash = hash.Hash160(t.Script)
|
||||
default:
|
||||
return nil, response.NewRPCError("Invalid transaction type", "", nil)
|
||||
}
|
||||
|
||||
return result.NewApplicationLog(appExecResult, scriptHash), nil
|
||||
}
|
||||
|
||||
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||
param, ok := ps.Value(0)
|
||||
if !ok {
|
||||
|
|
|
@ -40,6 +40,44 @@ type rpcTestCase struct {
|
|||
}
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
{
|
||||
name: "positive",
|
||||
params: `["d5cf936296de912aa4d051531bd8d25c7a58fb68fc7f87c8d3e6e85475187c08"]`,
|
||||
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||
res, ok := acc.(*result.ApplicationLog)
|
||||
|
||||
require.True(t, ok)
|
||||
|
||||
expectedTxHash := util.Uint256{0x8, 0x7c, 0x18, 0x75, 0x54, 0xe8, 0xe6, 0xd3, 0xc8, 0x87, 0x7f, 0xfc, 0x68, 0xfb, 0x58, 0x7a, 0x5c, 0xd2, 0xd8, 0x1b, 0x53, 0x51, 0xd0, 0xa4, 0x2a, 0x91, 0xde, 0x96, 0x62, 0x93, 0xcf, 0xd5}
|
||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||
assert.Equal(t, 1, len(res.Executions))
|
||||
assert.Equal(t, "Application", res.Executions[0].Trigger)
|
||||
assert.Equal(t, "HALT", res.Executions[0].VMState)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
params: `[]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid address",
|
||||
params: `["notahash"]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tx hash",
|
||||
params: `["d24cc1d52b5c0216cbf3835bb5bac8ccf32639fa1ab6627ec4e2b9f33f7ec02f"]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tx type",
|
||||
params: `["f9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed"]`,
|
||||
fail: true,
|
||||
},
|
||||
},
|
||||
"getaccountstate": {
|
||||
{
|
||||
name: "positive",
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/util"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
@ -169,6 +170,11 @@ func (c *Context) Dup() StackItem {
|
|||
return c
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (c *Context) ToContractParameter() smartcontract.Parameter {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func (c *Context) atBreakPoint() bool {
|
||||
for _, n := range c.breakPoints {
|
||||
if n == c.ip {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
||||
)
|
||||
|
||||
|
@ -17,6 +18,8 @@ type StackItem interface {
|
|||
Value() interface{}
|
||||
// Dup duplicates current StackItem.
|
||||
Dup() StackItem
|
||||
// ToContractParameter converts StackItem to smartcontract.Parameter
|
||||
ToContractParameter() smartcontract.Parameter
|
||||
}
|
||||
|
||||
func makeStackItem(v interface{}) StackItem {
|
||||
|
@ -115,6 +118,19 @@ func (i *StructItem) Dup() StackItem {
|
|||
return i
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *StructItem) ToContractParameter() smartcontract.Parameter {
|
||||
var value []smartcontract.Parameter
|
||||
for _, stackItem := range i.value {
|
||||
parameter := stackItem.ToContractParameter()
|
||||
value = append(value, parameter)
|
||||
}
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.ArrayType,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a Struct with all Struct fields copied by value.
|
||||
// Array fields are still copied by reference.
|
||||
func (i *StructItem) Clone() *StructItem {
|
||||
|
@ -162,6 +178,14 @@ func (i *BigIntegerItem) Dup() StackItem {
|
|||
return &BigIntegerItem{n.Set(i.value)}
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *BigIntegerItem) ToContractParameter() smartcontract.Parameter {
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.IntegerType,
|
||||
Value: i.value.Int64(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
@ -198,6 +222,14 @@ func (i *BoolItem) Dup() StackItem {
|
|||
return &BoolItem{i.value}
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *BoolItem) ToContractParameter() smartcontract.Parameter {
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.BoolType,
|
||||
Value: i.value,
|
||||
}
|
||||
}
|
||||
|
||||
// ByteArrayItem represents a byte array on the stack.
|
||||
type ByteArrayItem struct {
|
||||
value []byte
|
||||
|
@ -231,6 +263,14 @@ func (i *ByteArrayItem) Dup() StackItem {
|
|||
return &ByteArrayItem{a}
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *ByteArrayItem) ToContractParameter() smartcontract.Parameter {
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.ByteArrayType,
|
||||
Value: i.value,
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayItem represents a new ArrayItem object.
|
||||
type ArrayItem struct {
|
||||
value []StackItem
|
||||
|
@ -263,6 +303,19 @@ func (i *ArrayItem) Dup() StackItem {
|
|||
return i
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *ArrayItem) ToContractParameter() smartcontract.Parameter {
|
||||
var value []smartcontract.Parameter
|
||||
for _, stackItem := range i.value {
|
||||
parameter := stackItem.ToContractParameter()
|
||||
value = append(value, parameter)
|
||||
}
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.ArrayType,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// MapItem represents Map object.
|
||||
type MapItem struct {
|
||||
value map[interface{}]StackItem
|
||||
|
@ -280,7 +333,6 @@ func (i *MapItem) Value() interface{} {
|
|||
return i.value
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *MapItem) String() string {
|
||||
return "Map"
|
||||
}
|
||||
|
@ -297,6 +349,23 @@ func (i *MapItem) Dup() StackItem {
|
|||
return i
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *MapItem) ToContractParameter() smartcontract.Parameter {
|
||||
value := make(map[smartcontract.Parameter]smartcontract.Parameter)
|
||||
for key, val := range i.value {
|
||||
pValue := val.ToContractParameter()
|
||||
pKey := fromMapKey(key).ToContractParameter()
|
||||
if pKey.Type == smartcontract.ByteArrayType {
|
||||
pKey.Value = string(pKey.Value.([]byte))
|
||||
}
|
||||
value[pKey] = pValue
|
||||
}
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.MapType,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds key-value pair to the map.
|
||||
func (i *MapItem) Add(key, value StackItem) {
|
||||
i.value[toMapKey(key)] = value
|
||||
|
@ -316,6 +385,20 @@ func toMapKey(key StackItem) interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// fromMapKey converts map key to StackItem
|
||||
func fromMapKey(key interface{}) StackItem {
|
||||
switch t := key.(type) {
|
||||
case bool:
|
||||
return &BoolItem{value: t}
|
||||
case int64:
|
||||
return &BigIntegerItem{value: big.NewInt(t)}
|
||||
case string:
|
||||
return &ByteArrayItem{value: []byte(t)}
|
||||
default:
|
||||
panic("wrong key type")
|
||||
}
|
||||
}
|
||||
|
||||
// InteropItem represents interop data on the stack.
|
||||
type InteropItem struct {
|
||||
value interface{}
|
||||
|
@ -344,6 +427,14 @@ func (i *InteropItem) Dup() StackItem {
|
|||
return i
|
||||
}
|
||||
|
||||
// ToContractParameter implements StackItem interface.
|
||||
func (i *InteropItem) ToContractParameter() smartcontract.Parameter {
|
||||
return smartcontract.Parameter{
|
||||
Type: smartcontract.InteropInterfaceType,
|
||||
Value: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (i *InteropItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.value)
|
||||
|
|
91
pkg/vm/stack_item_test.go
Normal file
91
pkg/vm/stack_item_test.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package vm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
)
|
||||
|
||||
var toContractParameterTestCases = []struct {
|
||||
input StackItem
|
||||
result smartcontract.Parameter
|
||||
}{
|
||||
{
|
||||
input: NewStructItem([]StackItem{
|
||||
NewBigIntegerItem(1),
|
||||
NewBoolItem(true),
|
||||
}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
|
||||
{Type: smartcontract.IntegerType, Value: int64(1)},
|
||||
{Type: smartcontract.BoolType, Value: true},
|
||||
}},
|
||||
},
|
||||
{
|
||||
input: NewBoolItem(false),
|
||||
result: smartcontract.Parameter{Type: smartcontract.BoolType, Value: false},
|
||||
},
|
||||
{
|
||||
input: NewByteArrayItem([]byte{0x01, 0x02, 0x03}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
input: NewArrayItem([]StackItem{NewBigIntegerItem(2), NewBoolItem(true)}),
|
||||
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
|
||||
{Type: smartcontract.IntegerType, Value: int64(2)},
|
||||
{Type: smartcontract.BoolType, Value: true},
|
||||
}},
|
||||
},
|
||||
{
|
||||
input: NewInteropItem(nil),
|
||||
result: smartcontract.Parameter{Type: smartcontract.InteropInterfaceType, Value: nil},
|
||||
},
|
||||
{
|
||||
input: &MapItem{value: map[interface{}]StackItem{
|
||||
toMapKey(NewBigIntegerItem(1)): NewBoolItem(true),
|
||||
toMapKey(NewByteArrayItem([]byte("qwerty"))): NewBigIntegerItem(3),
|
||||
toMapKey(NewBoolItem(true)): NewBoolItem(false),
|
||||
}},
|
||||
result: smartcontract.Parameter{
|
||||
Type: smartcontract.MapType,
|
||||
Value: map[smartcontract.Parameter]smartcontract.Parameter{
|
||||
{Type: smartcontract.IntegerType, Value: int64(1)}: {Type: smartcontract.BoolType, Value: true},
|
||||
{Type: smartcontract.ByteArrayType, Value: "qwerty"}: {Type: smartcontract.IntegerType, Value: int64(3)},
|
||||
{Type: smartcontract.BoolType, Value: true}: {Type: smartcontract.BoolType, Value: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestToContractParameter(t *testing.T) {
|
||||
for _, tc := range toContractParameterTestCases {
|
||||
res := tc.input.ToContractParameter()
|
||||
assert.Equal(t, res, tc.result)
|
||||
}
|
||||
}
|
||||
|
||||
var fromMapKeyTestCases = []struct {
|
||||
input interface{}
|
||||
result StackItem
|
||||
}{
|
||||
{
|
||||
input: true,
|
||||
result: NewBoolItem(true),
|
||||
},
|
||||
{
|
||||
input: int64(4),
|
||||
result: NewBigIntegerItem(4),
|
||||
},
|
||||
{
|
||||
input: "qwerty",
|
||||
result: NewByteArrayItem([]byte("qwerty")),
|
||||
},
|
||||
}
|
||||
|
||||
func TestFromMapKey(t *testing.T) {
|
||||
for _, tc := range fromMapKeyTestCases {
|
||||
res := fromMapKey(tc.input)
|
||||
assert.Equal(t, res, tc.result)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue