From 7633439845173b1bd30e92959660df49623bd8da Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 13 May 2020 21:27:08 +0300 Subject: [PATCH] rpc/block: rework the way Block is JSONized Our block.Block was JSONized in a bit different fashion than result.Block in its NextConsensus and Index fields. It's not good for notifications because third-party clients would probably expect to see the same format. Also, using completely different Block representation is probably making our client a bit weaker as this representation is harder to use with other neo-go components. So use the same approach we took for Transactions and wrap block.Block which is to be serialized in proper way. Fix `Script` JSONization along the way, 3.0 node wraps it within `witnesses`. --- pkg/core/block/block.go | 54 +++++++++++- pkg/core/block/block_base.go | 76 +++++++++++++++-- pkg/core/block/block_test.go | 2 + pkg/core/block/consensus_data.go | 38 ++++++++- pkg/rpc/client/rpc_test.go | 123 +++++++++++++++++----------- pkg/rpc/client/wsclient_test.go | 2 +- pkg/rpc/response/result/block.go | 96 +++++++++++++--------- pkg/rpc/server/server_test.go | 5 +- pkg/rpc/server/subscription_test.go | 2 +- 9 files changed, 296 insertions(+), 102 deletions(-) diff --git a/pkg/core/block/block.go b/pkg/core/block/block.go index a9715d71e..14c39ce51 100644 --- a/pkg/core/block/block.go +++ b/pkg/core/block/block.go @@ -1,6 +1,7 @@ package block import ( + "encoding/json" "errors" "github.com/Workiva/go-datastructures/queue" @@ -19,10 +20,16 @@ type Block struct { ConsensusData ConsensusData `json:"consensus_data"` // Transaction list. - Transactions []*transaction.Transaction `json:"tx"` + Transactions []*transaction.Transaction // True if this block is created from trimmed data. - Trimmed bool `json:"-"` + Trimmed bool +} + +// auxBlock is used for JSON i/o. +type auxBlock struct { + ConsensusData ConsensusData `json:"consensus_data"` + Transactions []*transaction.Transaction `json:"tx"` } // Header returns the Header of the Block. @@ -179,3 +186,46 @@ func (b *Block) Compare(item queue.Item) int { return -1 } } + +// MarshalJSON implements json.Marshaler interface. +func (b Block) MarshalJSON() ([]byte, error) { + auxb, err := json.Marshal(auxBlock{ + ConsensusData: b.ConsensusData, + Transactions: b.Transactions, + }) + if err != nil { + return nil, err + } + baseBytes, err := json.Marshal(b.Base) + if err != nil { + return nil, err + } + + // Stitch them together. + if baseBytes[len(baseBytes)-1] != '}' || auxb[0] != '{' { + return nil, errors.New("can't merge internal jsons") + } + baseBytes[len(baseBytes)-1] = ',' + baseBytes = append(baseBytes, auxb[1:]...) + return baseBytes, nil +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (b *Block) UnmarshalJSON(data []byte) error { + // As Base and auxb are at the same level in json, + // do unmarshalling separately for both structs. + auxb := new(auxBlock) + err := json.Unmarshal(data, auxb) + if err != nil { + return err + } + base := new(Base) + err = json.Unmarshal(data, base) + if err != nil { + return err + } + b.Base = *base + b.Transactions = auxb.Transactions + b.ConsensusData = auxb.ConsensusData + return nil +} diff --git a/pkg/core/block/block_base.go b/pkg/core/block/block_base.go index 3b4073a5a..0a060d597 100644 --- a/pkg/core/block/block_base.go +++ b/pkg/core/block/block_base.go @@ -1,10 +1,13 @@ package block import ( + "encoding/json" + "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "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" ) @@ -12,31 +15,31 @@ import ( // Base holds the base info of a block type Base struct { // Version of the block. - Version uint32 `json:"version"` + Version uint32 // hash of the previous block. - PrevHash util.Uint256 `json:"previousblockhash"` + PrevHash util.Uint256 // Root hash of a transaction list. - MerkleRoot util.Uint256 `json:"merkleroot"` + MerkleRoot util.Uint256 // Timestamp is a millisecond-precision timestamp. // The time stamp of each block must be later than previous block's time stamp. // Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed. // The height of the block must be exactly equal to the height of the previous block plus 1. - Timestamp uint64 `json:"time"` + Timestamp uint64 // index/height of the block - Index uint32 `json:"height"` + Index uint32 // Contract address of the next miner - NextConsensus util.Uint160 `json:"next_consensus"` + NextConsensus util.Uint160 // Padding that is fixed to 1 _ uint8 // Script used to validate the block - Script transaction.Witness `json:"script"` + Script transaction.Witness // Hash of this block, created when binary encoded (double SHA256). hash util.Uint256 @@ -45,6 +48,20 @@ type Base struct { verificationHash util.Uint256 } +// baseAux is used to marshal/unmarshal to/from JSON, it's almost the same +// as original Base, but with Nonce and NextConsensus fields differing and +// Hash added. +type baseAux struct { + Hash util.Uint256 `json:"hash"` + Version uint32 `json:"version"` + PrevHash 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"` +} + // Verify verifies the integrity of the Base. func (b *Base) Verify() bool { // TODO: Need a persisted blockchain for this. @@ -136,3 +153,48 @@ func (b *Base) decodeHashableFields(br *io.BinReader) { b.createHash() } } + +// MarshalJSON implements json.Marshaler interface. +func (b Base) MarshalJSON() ([]byte, error) { + aux := baseAux{ + Hash: b.Hash(), + Version: b.Version, + PrevHash: b.PrevHash, + MerkleRoot: b.MerkleRoot, + Timestamp: b.Timestamp, + Index: b.Index, + NextConsensus: address.Uint160ToString(b.NextConsensus), + Witnesses: []transaction.Witness{b.Script}, + } + return json.Marshal(aux) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (b *Base) UnmarshalJSON(data []byte) error { + var aux = new(baseAux) + var nextC util.Uint160 + + err := json.Unmarshal(data, aux) + if err != nil { + return err + } + + nextC, err = address.StringToUint160(aux.NextConsensus) + if err != nil { + return err + } + if len(aux.Witnesses) != 1 { + return errors.New("wrong number of witnesses") + } + b.Version = aux.Version + b.PrevHash = aux.PrevHash + b.MerkleRoot = aux.MerkleRoot + b.Timestamp = aux.Timestamp + b.Index = aux.Index + b.NextConsensus = nextC + b.Script = aux.Witnesses[0] + if !aux.Hash.Equals(b.Hash()) { + return errors.New("json 'hash' doesn't match block hash") + } + return nil +} diff --git a/pkg/core/block/block_test.go b/pkg/core/block/block_test.go index f3461ffbd..b9f6dc72d 100644 --- a/pkg/core/block/block_test.go +++ b/pkg/core/block/block_test.go @@ -172,6 +172,8 @@ func TestBinBlockDecodeEncode(t *testing.T) { data, err := testserdes.EncodeBinary(&b) assert.NoError(t, err) assert.Equal(t, rawtx, hex.EncodeToString(data)) + + testserdes.MarshalUnmarshalJSON(t, &b, new(Block)) } func TestBlockSizeCalculation(t *testing.T) { diff --git a/pkg/core/block/consensus_data.go b/pkg/core/block/consensus_data.go index ef8cc6f95..29211ba26 100644 --- a/pkg/core/block/consensus_data.go +++ b/pkg/core/block/consensus_data.go @@ -1,6 +1,9 @@ package block import ( + "encoding/json" + "strconv" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/util" @@ -9,13 +12,19 @@ import ( // ConsensusData represents primary index and nonce of block in the chain. type ConsensusData struct { // Primary index - PrimaryIndex uint32 `json:"primary"` + PrimaryIndex uint32 // Random number - Nonce uint64 `json:"nonce"` + Nonce uint64 // Hash of the ConsensusData (single SHA256) hash util.Uint256 } +// jsonConsensusData is used for JSON I/O of ConsensusData. +type jsonConsensusData struct { + Primary uint32 `json:"primary"` + Nonce string `json:"nonce"` +} + // DecodeBinary implements Serializable interface. func (c *ConsensusData) DecodeBinary(br *io.BinReader) { c.PrimaryIndex = uint32(br.ReadVarUint()) @@ -50,3 +59,28 @@ func (c *ConsensusData) createHash() error { c.hash = hash.Sha256(b) return nil } + +// MarshalJSON implements json.Marshaler interface. +func (c ConsensusData) MarshalJSON() ([]byte, error) { + nonce := strconv.FormatUint(c.Nonce, 16) + for len(nonce) < 16 { + nonce = "0" + nonce + } + return json.Marshal(jsonConsensusData{Primary: c.PrimaryIndex, Nonce: nonce}) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (c *ConsensusData) UnmarshalJSON(data []byte) error { + jcd := new(jsonConsensusData) + err := json.Unmarshal(data, jcd) + if err != nil { + return err + } + nonce, err := strconv.ParseUint(jcd.Nonce, 16, 64) + if err != nil { + return err + } + c.PrimaryIndex = jcd.Primary + c.Nonce = nonce + return nil +} diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 3bcf13f7a..838a91ad2 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -3,6 +3,7 @@ package client import ( "context" "encoding/hex" + "fmt" "net/http" "net/http/httptest" "strings" @@ -156,12 +157,8 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (i interface{}, err error) { return c.GetBlockByIndexVerbose(202) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"witnesses":[{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`, result: func(c *Client) interface{} { - hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86") - if err != nil { - panic(err) - } nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22") if err != nil { panic(err) @@ -227,28 +224,47 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, } + var nonce uint64 + i, err := fmt.Sscanf("0000000000000457", "%016x", &nonce) + if i != 1 { + panic("can't decode nonce") + } + if err != nil { + panic(err) + } + nextCon, err := address.StringToUint160("AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL") + if err != nil { + panic(err) + } + blck := &block.Block{ + Base: block.Base{ + Version: 0, + PrevHash: prevBlockHash, + MerkleRoot: merkleRoot, + Timestamp: 1589300496, + Index: 202, + NextConsensus: nextCon, + Script: transaction.Witness{ + InvocationScript: invScript, + VerificationScript: verifScript, + }, + }, + ConsensusData: block.ConsensusData{ + PrimaryIndex: 0, + Nonce: nonce, + }, + Transactions: []*transaction.Transaction{tx}, + } // Update hashes for correct result comparison. _ = tx.Hash() + _ = blck.Hash() return &result.Block{ - Hash: hash, - Size: 781, - Version: 0, - NextBlockHash: &nextBlockHash, - PreviousBlockHash: prevBlockHash, - MerkleRoot: merkleRoot, - Time: 1589300496, - Index: 202, - NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL", - Confirmations: 6, - ConsensusData: result.ConsensusData{ - PrimaryIndex: 0, - Nonce: "0000000000000457", + Block: blck, + BlockMetadata: result.BlockMetadata{ + Size: 781, + Confirmations: 6, + NextBlockHash: &nextBlockHash, }, - Script: transaction.Witness{ - InvocationScript: invScript, - VerificationScript: verifScript, - }, - Tx: []*transaction.Transaction{tx}, } }, }, @@ -283,12 +299,8 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ } return c.GetBlockByHashVerbose(hash) }, - serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"script":{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"witnesses":[{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`, result: func(c *Client) interface{} { - hash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86") - if err != nil { - panic(err) - } nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22") if err != nil { panic(err) @@ -354,28 +366,47 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, } + var nonce uint64 + i, err := fmt.Sscanf("0000000000000457", "%016x", &nonce) + if i != 1 { + panic("can't decode nonce") + } + if err != nil { + panic(err) + } + nextCon, err := address.StringToUint160("AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL") + if err != nil { + panic(err) + } + blck := &block.Block{ + Base: block.Base{ + Version: 0, + PrevHash: prevBlockHash, + MerkleRoot: merkleRoot, + Timestamp: 1589300496, + Index: 202, + NextConsensus: nextCon, + Script: transaction.Witness{ + InvocationScript: invScript, + VerificationScript: verifScript, + }, + }, + ConsensusData: block.ConsensusData{ + PrimaryIndex: 0, + Nonce: nonce, + }, + Transactions: []*transaction.Transaction{tx}, + } // Update hashes for correct result comparison. _ = tx.Hash() + _ = blck.Hash() return &result.Block{ - Hash: hash, - Size: 781, - Version: 0, - NextBlockHash: &nextBlockHash, - PreviousBlockHash: prevBlockHash, - MerkleRoot: merkleRoot, - Time: 1589300496, - Index: 202, - NextConsensus: "AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL", - Confirmations: 6, - ConsensusData: result.ConsensusData{ - PrimaryIndex: 0, - Nonce: "0000000000000457", + Block: blck, + BlockMetadata: result.BlockMetadata{ + Size: 781, + Confirmations: 6, + NextBlockHash: &nextBlockHash, }, - Script: transaction.Witness{ - InvocationScript: invScript, - VerificationScript: verifScript, - }, - Tx: []*transaction.Transaction{tx}, } }, }, diff --git a/pkg/rpc/client/wsclient_test.go b/pkg/rpc/client/wsclient_test.go index 494d31072..55224cf35 100644 --- a/pkg/rpc/client/wsclient_test.go +++ b/pkg/rpc/client/wsclient_test.go @@ -117,7 +117,7 @@ func TestWSClientEvents(t *testing.T) { `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","executions":[{"trigger":"Application","contract":"0x0000000000000000000000000000000000000000","vmstate":"HALT","gas_consumed":"2.291","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"7472616e73666572"},{"type":"Array","value":[{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"7472616e73666572"},{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}}]}]}]}`, `{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"7472616e73666572"},{"type":"Array","value":[{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}]}}]}`, `{"jsonrpc":"2.0","method":"transaction_added","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","size":277,"type":"InvocationTransaction","version":1,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0037721","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"vin":[],"vout":[],"scripts":[{"invocation":"0c4027727296b84853c5d9e07fb8a40e885246ae25641383b16eefbe92027ecb1635b794aacf6bbfc3e828c73829b14791c483d19eb758b57638e3191393dbf2d288","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"01e8030c14316e851039019d39dfc2c37d6c3fee19fd5809870c14769162241eedf97c2481652adf1ba0f5bf57431b13c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238"}]}`, - `{"jsonrpc":"2.0","method":"block_added","params":[{"version":0,"previousblockhash":"0x04f7580b111ec75f0ce68d3a9fd70a0544b4521b4a98541694d8575c548b759e","merkleroot":"0xb2c7230ebee4cb83bc03afadbba413e6bca8fcdeaf9c077bea060918da0e52a1","time":1590006200,"height":207,"next_consensus":"0x4138145d67638db97b402ee0b6751ef16253ecab","script":{"invocation":"0c4063429fca5ff75c964d9e38179c75978e33f8174d91a780c2e825265cf2447281594afdd5f3e216dcaf5ff0693aec83f415996cf224454495495f6bd0a4c5d08f0c4099680903a954278580d8533121c2cd3e53a089817b6a784901ec06178a60b5f1da6e70422bdcadc89029767e08d66ce4180b99334cb2d42f42e4216394af15920c4067d5e362189e48839a24e187c59d46f5d9db862c8a029777f1548b19632bfdc73ad373827ed02369f925e89c2303b64e6b9838dca229949b9b9d3bd4c0c3ed8f0c4021d4c00d4522805883f1db929554441bcbbee127c48f6b7feeeb69a72a78c7f0a75011663e239c0820ef903f36168f42936de10f0ef20681cb735a4b53d0390f","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"},"consensus_data":{"primary":0,"nonce":1111},"tx":[{"txid":"0xf736cd91ab84062a21a09b424346b241987f6245ffe8c2b2db39d595c3c222f7","size":204,"type":"InvocationTransaction","version":1,"nonce":8,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0030421","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[],"vout":[],"scripts":[{"invocation":"0c4016e7a112742409cdfaad89dcdbcb52c94c5c1a69dfe5d8b999649eaaa787e31ca496d1734d6ea606c749ad36e9a88892240ae59e0efa7f544e0692124898d512","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"10c00c04696e69740c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b52"},{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","size":277,"type":"InvocationTransaction","version":1,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0037721","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"vin":[],"vout":[],"scripts":[{"invocation":"0c4027727296b84853c5d9e07fb8a40e885246ae25641383b16eefbe92027ecb1635b794aacf6bbfc3e828c73829b14791c483d19eb758b57638e3191393dbf2d288","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"01e8030c14316e851039019d39dfc2c37d6c3fee19fd5809870c14769162241eedf97c2481652adf1ba0f5bf57431b13c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238"}]}]}`, + `{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x239fea00c54c2f6812612874183b72bef4473fcdf68bf8da08d74fd5b6cab030","version":0,"previousblockhash":"0x04f7580b111ec75f0ce68d3a9fd70a0544b4521b4a98541694d8575c548b759e","merkleroot":"0xb2c7230ebee4cb83bc03afadbba413e6bca8fcdeaf9c077bea060918da0e52a1","time":1590006200,"index":207,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"0c4063429fca5ff75c964d9e38179c75978e33f8174d91a780c2e825265cf2447281594afdd5f3e216dcaf5ff0693aec83f415996cf224454495495f6bd0a4c5d08f0c4099680903a954278580d8533121c2cd3e53a089817b6a784901ec06178a60b5f1da6e70422bdcadc89029767e08d66ce4180b99334cb2d42f42e4216394af15920c4067d5e362189e48839a24e187c59d46f5d9db862c8a029777f1548b19632bfdc73ad373827ed02369f925e89c2303b64e6b9838dca229949b9b9d3bd4c0c3ed8f0c4021d4c00d4522805883f1db929554441bcbbee127c48f6b7feeeb69a72a78c7f0a75011663e239c0820ef903f36168f42936de10f0ef20681cb735a4b53d0390f","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"consensus_data":{"primary":0,"nonce":"0000000000000457"},"tx":[{"txid":"0xf736cd91ab84062a21a09b424346b241987f6245ffe8c2b2db39d595c3c222f7","size":204,"type":"InvocationTransaction","version":1,"nonce":8,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0030421","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[],"vout":[],"scripts":[{"invocation":"0c4016e7a112742409cdfaad89dcdbcb52c94c5c1a69dfe5d8b999649eaaa787e31ca496d1734d6ea606c749ad36e9a88892240ae59e0efa7f544e0692124898d512","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"10c00c04696e69740c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b52"},{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","size":277,"type":"InvocationTransaction","version":1,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0037721","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"vin":[],"vout":[],"scripts":[{"invocation":"0c4027727296b84853c5d9e07fb8a40e885246ae25641383b16eefbe92027ecb1635b794aacf6bbfc3e828c73829b14791c483d19eb758b57638e3191393dbf2d288","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"01e8030c14316e851039019d39dfc2c37d6c3fee19fd5809870c14769162241eedf97c2481652adf1ba0f5bf57431b13c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238"}]}]}`, `{"jsonrpc":"2.0","method":"event_missed","params":[]}`, } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/pkg/rpc/response/result/block.go b/pkg/rpc/response/result/block.go index 3e18d6133..d9517a757 100644 --- a/pkg/rpc/response/result/block.go +++ b/pkg/rpc/response/result/block.go @@ -1,65 +1,40 @@ package result import ( - "fmt" + "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" ) type ( - // ConsensusData is a wrapper for block.ConsensusData - ConsensusData struct { - PrimaryIndex uint32 `json:"primary"` - Nonce string `json:"nonce"` - } - // Block wrapper used for the representation of // block.Block / block.Base on the RPC Server. Block struct { - Hash util.Uint256 `json:"hash"` - Size int `json:"size"` - Version uint32 `json:"version"` - NextBlockHash *util.Uint256 `json:"nextblockhash,omitempty"` - PreviousBlockHash util.Uint256 `json:"previousblockhash"` - MerkleRoot util.Uint256 `json:"merkleroot"` - Time uint64 `json:"time"` - Index uint32 `json:"index"` - ConsensusData ConsensusData `json:"consensus_data"` - NextConsensus string `json:"nextconsensus"` + *block.Block + BlockMetadata + } - Confirmations uint32 `json:"confirmations"` - - Script transaction.Witness `json:"script"` - - Tx []*transaction.Transaction `json:"tx"` + // BlockMetadata is an additional metadata added to standard + // block.Block. + BlockMetadata struct { + Size int `json:"size"` + NextBlockHash *util.Uint256 `json:"nextblockhash,omitempty"` + Confirmations uint32 `json:"confirmations"` } ) // NewBlock creates a new Block wrapper. func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block { res := Block{ - Version: b.Version, - Hash: b.Hash(), - Size: io.GetVarSize(b), - PreviousBlockHash: b.PrevHash, - MerkleRoot: b.MerkleRoot, - Time: b.Timestamp, - Index: b.Index, - ConsensusData: ConsensusData{ - PrimaryIndex: b.ConsensusData.PrimaryIndex, - Nonce: fmt.Sprintf("%016x", b.ConsensusData.Nonce), + Block: b, + BlockMetadata: BlockMetadata{ + Size: io.GetVarSize(b), + Confirmations: chain.BlockHeight() - b.Index - 1, }, - NextConsensus: address.Uint160ToString(b.NextConsensus), - Confirmations: chain.BlockHeight() - b.Index - 1, - - Script: b.Script, - - Tx: b.Transactions, } hash := chain.GetHeaderHash(int(b.Index) + 1) @@ -69,3 +44,44 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block { return res } + +// MarshalJSON implements json.Marshaler interface. +func (b Block) MarshalJSON() ([]byte, error) { + output, err := json.Marshal(b.BlockMetadata) + if err != nil { + return nil, err + } + baseBytes, err := json.Marshal(b.Block) + 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 (b *Block) 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) + base := new(block.Block) + err := json.Unmarshal(data, meta) + if err != nil { + return err + } + err = json.Unmarshal(data, base) + if err != nil { + return err + } + b.Block = base + b.BlockMetadata = *meta + return nil +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index f36d6c651..5af4908c1 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -384,9 +384,8 @@ var rpcTestCases = map[string][]rpcTestCase{ block, err := e.chain.GetBlock(e.chain.GetHeaderHash(3)) require.NoErrorf(t, err, "could not get block") - assert.Equal(t, block.Hash(), res.Hash) - for i := range res.Tx { - tx := res.Tx[i] + assert.Equal(t, block.Hash(), res.Hash()) + for i, tx := range res.Transactions { require.Equal(t, transaction.ContractType, tx.Type) actualTx := block.Transactions[i] diff --git a/pkg/rpc/server/subscription_test.go b/pkg/rpc/server/subscription_test.go index 162674549..ef0ba8155 100644 --- a/pkg/rpc/server/subscription_test.go +++ b/pkg/rpc/server/subscription_test.go @@ -228,7 +228,7 @@ func TestFilteredSubscriptions(t *testing.T) { resp := getNotification(t, respMsgs) rmap := resp.Payload[0].(map[string]interface{}) if resp.Event == response.BlockEventID { - index := rmap["height"].(float64) + index := rmap["index"].(float64) if uint32(index) == lastBlock { break }