diff --git a/docs/rpc.md b/docs/rpc.md index 31353456b..7790bad2b 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -88,6 +88,11 @@ feature is not supported by the C# node. It's possible to get non-native contract state by its ID, unlike with C# node where it only works for native contracts. +##### `getrawtransaction` + +VM state is included to verbose response along with other transaction fields if +the transaction is already on chain. + ##### `getstorage` This method doesn't work for the Ledger contract, you can get data via regular diff --git a/pkg/core/block/header.go b/pkg/core/block/header.go index b7f78744f..154d512d9 100644 --- a/pkg/core/block/header.go +++ b/pkg/core/block/header.go @@ -157,7 +157,7 @@ func (b Header) MarshalJSON() ([]byte, error) { PrevHash: b.PrevHash, MerkleRoot: b.MerkleRoot, Timestamp: b.Timestamp, - Nonce: fmt.Sprintf("%016x", b.Nonce), + Nonce: fmt.Sprintf("%016X", b.Nonce), Index: b.Index, PrimaryIndex: b.PrimaryIndex, NextConsensus: address.Uint160ToString(b.NextConsensus), diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 7c9ab541d..908b8a6ac 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -140,6 +140,7 @@ func NewContractMD(name string, id int32) *ContractMD { // Therefore values are taken from C# node. c.NEF.Header.Compiler = "neo-core-v3.0" c.NEF.Header.Magic = nef.Magic + c.NEF.Tokens = []nef.MethodToken{} // avoid `nil` result during JSON marshalling c.Hash = state.CreateContractHash(util.Uint160{}, 0, c.Name) c.Manifest = *manifest.DefaultManifest(name) diff --git a/pkg/core/native/native_nep17.go b/pkg/core/native/native_nep17.go index 3b2695fa8..4cc48db3a 100644 --- a/pkg/core/native/native_nep17.go +++ b/pkg/core/native/native_nep17.go @@ -294,6 +294,9 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount } func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method { + if len(ps) == 0 { + ps = []manifest.Parameter{} + } return &manifest.Method{ Name: name, Parameters: ps, diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 097543292..4641f2517 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -264,17 +264,12 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ result: func(c *Client) interface{} { b := getResultBlock1() return &result.Header{ - Hash: b.Hash(), - Size: 457, - Version: b.Version, - NextBlockHash: b.NextBlockHash, - PrevBlockHash: b.PrevHash, - MerkleRoot: b.MerkleRoot, - Timestamp: b.Timestamp, - Index: b.Index, - NextConsensus: address.Uint160ToString(b.NextConsensus), - Witnesses: []transaction.Witness{b.Script}, - Confirmations: b.Confirmations, + Header: b.Header, + BlockMetadata: result.BlockMetadata{ + Size: 457, + NextBlockHash: b.NextBlockHash, + Confirmations: b.Confirmations, + }, } }, }, diff --git a/pkg/rpc/response/result/block_header.go b/pkg/rpc/response/result/block_header.go index 6e087883e..c68e7b38a 100644 --- a/pkg/rpc/response/result/block_header.go +++ b/pkg/rpc/response/result/block_header.go @@ -1,10 +1,11 @@ package result import ( + "encoding/json" + "errors" + "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" - "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -13,33 +14,19 @@ type ( // Header wrapper used for the representation of // block header on the RPC Server. Header struct { - Hash util.Uint256 `json:"hash"` - Size int `json:"size"` - Version uint32 `json:"version"` - PrevBlockHash util.Uint256 `json:"previousblockhash"` - MerkleRoot util.Uint256 `json:"merkleroot"` - Timestamp uint64 `json:"time"` - Index uint32 `json:"index"` - NextConsensus string `json:"nextconsensus"` - Witnesses []transaction.Witness `json:"witnesses"` - Confirmations uint32 `json:"confirmations"` - NextBlockHash *util.Uint256 `json:"nextblockhash,omitempty"` + block.Header + BlockMetadata } ) // NewHeader creates a new Header wrapper. func NewHeader(h *block.Header, chain blockchainer.Blockchainer) Header { res := Header{ - Hash: h.Hash(), - Size: io.GetVarSize(h), - Version: h.Version, - PrevBlockHash: h.PrevHash, - MerkleRoot: h.MerkleRoot, - Timestamp: h.Timestamp, - Index: h.Index, - NextConsensus: address.Uint160ToString(h.NextConsensus), - Witnesses: []transaction.Witness{h.Script}, - Confirmations: chain.BlockHeight() - h.Index + 1, + Header: *h, + BlockMetadata: BlockMetadata{ + Size: io.GetVarSize(h), + Confirmations: chain.BlockHeight() - h.Index + 1, + }, } hash := chain.GetHeaderHash(int(h.Index) + 1) @@ -48,3 +35,42 @@ func NewHeader(h *block.Header, chain blockchainer.Blockchainer) Header { } return res } + +// MarshalJSON implements json.Marshaler interface. +func (h Header) MarshalJSON() ([]byte, error) { + output, err := json.Marshal(h.BlockMetadata) + if err != nil { + return nil, err + } + baseBytes, err := json.Marshal(h.Header) + if err != nil { + return nil, err + } + + // We have to keep both "fields" at the same level in json in order to + // match C# API, so there's no way to marshall Block correctly with + // standard json.Marshaller tool. + if output[len(output)-1] != '}' || baseBytes[0] != '{' { + return nil, errors.New("can't merge internal jsons") + } + output[len(output)-1] = ',' + output = append(output, baseBytes[1:]...) + return output, nil +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (h *Header) UnmarshalJSON(data []byte) error { + // As block.Block and BlockMetadata are at the same level in json, + // do unmarshalling separately for both structs. + meta := new(BlockMetadata) + err := json.Unmarshal(data, meta) + if err != nil { + return err + } + err = json.Unmarshal(data, &h.Header) + if err != nil { + return err + } + h.BlockMetadata = *meta + return nil +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 8bd2e26a3..20d632a1e 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -548,8 +548,9 @@ func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response return hashList, nil } return result.RawMempool{ - Height: s.chain.BlockHeight(), - Verified: hashList, + Height: s.chain.BlockHeight(), + Verified: hashList, + Unverified: []util.Uint256{}, // avoid `null` result }, nil } @@ -1280,11 +1281,12 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response. tx := &transaction.Transaction{} if len(reqParams) > 1 { - signers, _, err := reqParams[1].GetSignersWithWitnesses() + signers, witnesses, err := reqParams[1].GetSignersWithWitnesses() if err != nil { return nil, response.ErrInvalidParams } tx.Signers = signers + tx.Scripts = witnesses } if len(tx.Signers) == 0 { tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c7f11b944..66260c311 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -1455,17 +1455,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] t.Run("verbose != 0", func(t *testing.T) { nextHash := chain.GetHeaderHash(int(hdr.Index) + 1) expected := &result.Header{ - Hash: hdr.Hash(), - Size: io.GetVarSize(hdr), - Version: hdr.Version, - PrevBlockHash: hdr.PrevHash, - MerkleRoot: hdr.MerkleRoot, - Timestamp: hdr.Timestamp, - Index: hdr.Index, - NextConsensus: address.Uint160ToString(hdr.NextConsensus), - Witnesses: []transaction.Witness{hdr.Script}, - Confirmations: e.chain.BlockHeight() - hdr.Index + 1, - NextBlockHash: &nextHash, + Header: *hdr, + BlockMetadata: result.BlockMetadata{ + Size: io.GetVarSize(hdr), + NextBlockHash: &nextHash, + Confirmations: e.chain.BlockHeight() - hdr.Index + 1, + }, } rpc := fmt.Sprintf(rpc, `["`+testHeaderHash+`", 2]`) diff --git a/pkg/smartcontract/nef/nef_test.go b/pkg/smartcontract/nef/nef_test.go index 03d3af86c..9bd8873e1 100644 --- a/pkg/smartcontract/nef/nef_test.go +++ b/pkg/smartcontract/nef/nef_test.go @@ -69,6 +69,14 @@ func TestEncodeDecodeBinary(t *testing.T) { expected.Header.Magic = Magic testserdes.EncodeDecodeBinary(t, expected, &File{}) }) + t.Run("positive with empty tokens", func(t *testing.T) { + expected.Script = script + // Check `Tokens` won't be deserialized to `nil`. We expect them to be non-nil slice in the related code. + expected.Tokens = []MethodToken{} + expected.Checksum = expected.CalculateChecksum() + expected.Header.Magic = Magic + testserdes.EncodeDecodeBinary(t, expected, &File{}) + }) t.Run("invalid reserved bytes", func(t *testing.T) { expected.Script = script expected.Tokens = expected.Tokens[:0]