diff --git a/pkg/core/block/block.go b/pkg/core/block/block.go index 37ec73847..8ecf65b99 100644 --- a/pkg/core/block/block.go +++ b/pkg/core/block/block.go @@ -1,6 +1,7 @@ package block import ( + "encoding/json" "errors" "fmt" @@ -17,10 +18,15 @@ type Block struct { Base // Transaction list. - Transactions []*transaction.Transaction `json:"tx"` + Transactions []*transaction.Transaction // True if this block is created from trimmed data. - Trimmed bool `json:"-"` + Trimmed bool +} + +// auxTxes is used for JSON i/o. +type auxTxes struct { + Transactions []*transaction.Transaction `json:"tx"` } // Header returns the Header of the Block. @@ -149,3 +155,42 @@ func (b *Block) Compare(item queue.Item) int { return -1 } } + +// MarshalJSON implements json.Marshaler interface. +func (b Block) MarshalJSON() ([]byte, error) { + txes, err := json.Marshal(auxTxes{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] != '}' || txes[0] != '{' { + return nil, errors.New("can't merge internal jsons") + } + baseBytes[len(baseBytes)-1] = ',' + baseBytes = append(baseBytes, txes[1:]...) + return baseBytes, nil +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (b *Block) UnmarshalJSON(data []byte) error { + // As Base and txes are at the same level in json, + // do unmarshalling separately for both structs. + txes := new(auxTxes) + err := json.Unmarshal(data, txes) + if err != nil { + return err + } + base := new(Base) + err = json.Unmarshal(data, base) + if err != nil { + return err + } + b.Base = *base + b.Transactions = txes.Transactions + return nil +} diff --git a/pkg/core/block/block_base.go b/pkg/core/block/block_base.go index e2dc11cbf..dfd4098ba 100644 --- a/pkg/core/block/block_base.go +++ b/pkg/core/block/block_base.go @@ -1,10 +1,14 @@ package block import ( + "encoding/json" + "errors" "fmt" + "strconv" "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,33 +16,33 @@ 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 // 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 uint32 `json:"time"` + Timestamp uint32 // index/height of the block - Index uint32 `json:"height"` + Index uint32 // Random number also called nonce - ConsensusData uint64 `json:"nonce"` + ConsensusData uint64 // 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 @@ -47,6 +51,21 @@ 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 uint32 `json:"time"` + Index uint32 `json:"index"` + Nonce string `json:"nonce"` + NextConsensus string `json:"nextconsensus"` + Script transaction.Witness `json:"script"` +} + // Verify verifies the integrity of the Base. func (b *Base) Verify() bool { // TODO: Need a persisted blockchain for this. @@ -140,3 +159,56 @@ func (b *Base) decodeHashableFields(br *io.BinReader) { b.createHash() } } + +// MarshalJSON implements json.Marshaler interface. +func (b Base) MarshalJSON() ([]byte, error) { + nonce := strconv.FormatUint(b.ConsensusData, 16) + for len(nonce) < 16 { + nonce = "0" + nonce + } + aux := baseAux{ + Hash: b.Hash(), + Version: b.Version, + PrevHash: b.PrevHash, + MerkleRoot: b.MerkleRoot, + Timestamp: b.Timestamp, + Index: b.Index, + Nonce: nonce, + NextConsensus: address.Uint160ToString(b.NextConsensus), + Script: b.Script, + } + return json.Marshal(aux) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (b *Base) UnmarshalJSON(data []byte) error { + var aux = new(baseAux) + var nonce uint64 + var nextC util.Uint160 + + err := json.Unmarshal(data, aux) + if err != nil { + return err + } + + nonce, err = strconv.ParseUint(aux.Nonce, 16, 64) + if err != nil { + return err + } + nextC, err = address.StringToUint160(aux.NextConsensus) + if err != nil { + return err + } + b.Version = aux.Version + b.PrevHash = aux.PrevHash + b.MerkleRoot = aux.MerkleRoot + b.Timestamp = aux.Timestamp + b.Index = aux.Index + b.ConsensusData = nonce + b.NextConsensus = nextC + b.Script = aux.Script + 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 b8a7d1a8c..4456b681f 100644 --- a/pkg/core/block/block_test.go +++ b/pkg/core/block/block_test.go @@ -188,6 +188,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/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 0191e82d4..ae96d19c2 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" @@ -14,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" @@ -158,10 +160,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xe93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c","size":452,"version":0,"nextblockhash":"0xcc37d5bc460e72c9423015cb8d579c13e7b03b93bfaa1a23cf4fa777988e035f","previousblockhash":"0x996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099","merkleroot":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","time":1541215200,"index":1,"nonce":"51b484a2fe49ed4d","nextconsensus":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","confirmations":10534,"script":{"invocation":"40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"txid":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","size":10,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[],"sys_fee":"0","net_fee":"0","nonce":4266257741}]}}`, result: func(c *Client) interface{} { - hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") - if err != nil { - panic(err) - } nextBlockHash, err := util.Uint256DecodeStringLE("cc37d5bc460e72c9423015cb8d579c13e7b03b93bfaa1a23cf4fa777988e035f") if err != nil { panic(err) @@ -192,31 +190,48 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Scripts: []transaction.Witness{}, Trimmed: false, } - // Update hashes for correct result comparison. - _ = tx.Hash() - return &result.Block{ - Hash: hash, - Size: 452, - Version: 0, - NextBlockHash: &nextBlockHash, - PreviousBlockHash: prevBlockHash, - MerkleRoot: merkleRoot, - Time: 1541215200, - Index: 1, - Nonce: "51b484a2fe49ed4d", - NextConsensus: "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", - Confirmations: 10534, + var nonce uint64 + i, err := fmt.Sscanf("51b484a2fe49ed4d", "%016x", &nonce) + if i != 1 { + panic("can't decode nonce") + } + if err != nil { + panic(err) + } + nextCon, err := address.StringToUint160("AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU") + if err != nil { + panic(err) + } + base := &block.Base{ + Version: 0, + PrevHash: prevBlockHash, + MerkleRoot: merkleRoot, + Timestamp: 1541215200, + Index: 1, + ConsensusData: nonce, + NextConsensus: nextCon, Script: transaction.Witness{ InvocationScript: invScript, VerificationScript: verifScript, }, - Tx: []result.Tx{{ - Transaction: tx, - Fees: result.Fees{ - SysFee: 0, - NetFee: 0, - }, - }}, + } + // Update hashes for correct result comparison. + _ = tx.Hash() + _ = base.Hash() + return &result.Block{ + Base: base, + BlockMetadataAndTx: result.BlockMetadataAndTx{ + Size: 452, + Confirmations: 10534, + NextBlockHash: &nextBlockHash, + Tx: []result.Tx{{ + Transaction: tx, + Fees: result.Fees{ + SysFee: 0, + NetFee: 0, + }, + }}, + }, } }, }, @@ -253,10 +268,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xe93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c","size":452,"version":0,"nextblockhash":"0xcc37d5bc460e72c9423015cb8d579c13e7b03b93bfaa1a23cf4fa777988e035f","previousblockhash":"0x996e37358dc369912041f966f8c5d8d3a8255ba5dcbd3447f8a82b55db869099","merkleroot":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","time":1541215200,"index":1,"nonce":"51b484a2fe49ed4d","nextconsensus":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","confirmations":10534,"script":{"invocation":"40356a91d94e398170e47447d6a0f60aa5470e209782a5452403115a49166db3e1c4a3898122db19f779c30f8ccd0b7d401acdf71eda340655e4ae5237a64961bf4034dd47955e5a71627dafc39dd92999140e9eaeec6b11dbb2b313efa3f1093ed915b4455e199c69ec53778f94ffc236b92f8b97fff97a1f6bbb3770c0c0b3844a40fbe743bd5c90b2f5255e0b073281d7aeb2fb516572f36bec8446bcc37ac755cbf10d08b16c95644db1b2dddc2df5daa377880b20198fc7b967ac6e76474b22df","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"txid":"0xcb6ddb5f99d6af4c94a6c396d5294472f2eebc91a2c933e0f527422296fa9fb2","size":10,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[],"sys_fee":"0","net_fee":"0","nonce":4266257741}]}}`, result: func(c *Client) interface{} { - hash, err := util.Uint256DecodeStringLE("e93d17a52967f9e69314385482bf86f85260e811b46bf4d4b261a7f4135a623c") - if err != nil { - panic(err) - } nextBlockHash, err := util.Uint256DecodeStringLE("cc37d5bc460e72c9423015cb8d579c13e7b03b93bfaa1a23cf4fa777988e035f") if err != nil { panic(err) @@ -277,6 +288,18 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ if err != nil { panic(err) } + var nonce uint64 + i, err := fmt.Sscanf("51b484a2fe49ed4d", "%016x", &nonce) + if i != 1 { + panic("can't decode nonce") + } + if err != nil { + panic(err) + } + nextCon, err := address.StringToUint160("AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU") + if err != nil { + panic(err) + } tx := &transaction.Transaction{ Type: transaction.MinerType, Version: 0, @@ -287,31 +310,36 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Scripts: []transaction.Witness{}, Trimmed: false, } - // Update hashes for correct result comparison. - _ = tx.Hash() - return &result.Block{ - Hash: hash, - Size: 452, - Version: 0, - NextBlockHash: &nextBlockHash, - PreviousBlockHash: prevBlockHash, - MerkleRoot: merkleRoot, - Time: 1541215200, - Index: 1, - Nonce: "51b484a2fe49ed4d", - NextConsensus: "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU", - Confirmations: 10534, + base := &block.Base{ + Version: 0, + PrevHash: prevBlockHash, + MerkleRoot: merkleRoot, + Timestamp: 1541215200, + Index: 1, + ConsensusData: nonce, + NextConsensus: nextCon, Script: transaction.Witness{ InvocationScript: invScript, VerificationScript: verifScript, }, - Tx: []result.Tx{{ - Transaction: tx, - Fees: result.Fees{ - SysFee: 0, - NetFee: 0, - }, - }}, + } + // Update hashes for correct result comparison. + _ = tx.Hash() + _ = base.Hash() + return &result.Block{ + Base: base, + BlockMetadataAndTx: result.BlockMetadataAndTx{ + Size: 452, + Confirmations: 10534, + NextBlockHash: &nextBlockHash, + Tx: []result.Tx{{ + Transaction: tx, + Fees: result.Fees{ + SysFee: 0, + NetFee: 0, + }, + }}, + }, } }, }, diff --git a/pkg/rpc/client/wsclient_test.go b/pkg/rpc/client/wsclient_test.go index 63643bdfb..f60f3a978 100644 --- a/pkg/rpc/client/wsclient_test.go +++ b/pkg/rpc/client/wsclient_test.go @@ -116,7 +116,7 @@ func TestWSClientEvents(t *testing.T) { `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","executions":[{"trigger":"Application","contract":"0x0000000000000000000000000000000000000000","vmstate":"HALT","gas_consumed":"1.048","stack":[{"type":"Integer","value":"1"}],"notifications":[{"contract":"0xc2789e5ab9bab828743833965b1df0d5fbcc206f","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"507574"},{"type":"Array","value":[{"type":"ByteArray","value":"746573746b6579"},{"type":"ByteArray","value":"7465737476616c7565"}]}]}}]}]}]}`, `{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0xc2789e5ab9bab828743833965b1df0d5fbcc206f","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"507574"},{"type":"Array","value":[{"type":"ByteArray","value":"746573746b6579"},{"type":"ByteArray","value":"7465737476616c7565"}]}]}}]}`, `{"jsonrpc":"2.0","method":"transaction_added","params":[{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","size":60,"type":"InvocationTransaction","version":1,"attributes":[],"vin":[],"vout":[],"scripts":[],"script":"097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2"}]}`, - `{"jsonrpc":"2.0","method":"block_added","params":[{"version":0,"previousblockhash":"0x33f3e0e24542b2ec3b6420e6881c31f6460a39a4e733d88f7557cbcc3b5ed560","merkleroot":"0x9d922c5cfd4c8cd1da7a6b2265061998dc438bd0dea7145192e2858155e6c57a","time":1586154525,"height":205,"nonce":1111,"next_consensus":"0xa21e4f7178607089e4fe9fab1300d1f5a3d348be","script":{"invocation":"4047a444a51218ac856f1cbc629f251c7c88187910534d6ba87847c86a9a73ed4951d203fd0a87f3e65657a7259269473896841f65c0a0c8efc79d270d917f4ff640435ee2f073c94a02f0276dfe4465037475e44e1c34c0decb87ec9c2f43edf688059fc4366a41c673d72ba772b4782c39e79f01cb981247353216d52d2df1651140527eb0dfd80a800fdd7ac8fbe68fc9366db2d71655d8ba235525a97a69a7181b1e069b82091be711c25e504a17c3c55eee6e76e6af13cb488fbe35d5c5d025c34041f39a02ebe9bb08be0e4aaa890f447dc9453209bbfb4705d8f2d869c2b55ee2d41dbec2ee476a059d77fb7c26400284328d05aece5f3168b48f1db1c6f7be0b","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"txid":"0xf9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed","size":10,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[]},{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","size":60,"type":"InvocationTransaction","version":1,"attributes":[],"vin":[],"vout":[],"scripts":[],"script":"097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2"}]}]}`, + `{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x48fba8aebf88278818a3dc0caecb230873d1d4ce1ea8bf473634317f94a609e5","version":0,"previousblockhash":"0x33f3e0e24542b2ec3b6420e6881c31f6460a39a4e733d88f7557cbcc3b5ed560","merkleroot":"0x9d922c5cfd4c8cd1da7a6b2265061998dc438bd0dea7145192e2858155e6c57a","time":1586154525,"index":205,"nonce":"0000000000000457","nextconsensus":"AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU","script":{"invocation":"4047a444a51218ac856f1cbc629f251c7c88187910534d6ba87847c86a9a73ed4951d203fd0a87f3e65657a7259269473896841f65c0a0c8efc79d270d917f4ff640435ee2f073c94a02f0276dfe4465037475e44e1c34c0decb87ec9c2f43edf688059fc4366a41c673d72ba772b4782c39e79f01cb981247353216d52d2df1651140527eb0dfd80a800fdd7ac8fbe68fc9366db2d71655d8ba235525a97a69a7181b1e069b82091be711c25e504a17c3c55eee6e76e6af13cb488fbe35d5c5d025c34041f39a02ebe9bb08be0e4aaa890f447dc9453209bbfb4705d8f2d869c2b55ee2d41dbec2ee476a059d77fb7c26400284328d05aece5f3168b48f1db1c6f7be0b","verification":"532102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd622102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc22103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee69954ae"},"tx":[{"txid":"0xf9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed","size":10,"type":"MinerTransaction","version":0,"attributes":[],"vin":[],"vout":[],"scripts":[]},{"txid":"0x93670859cc8a42f6ea994869c944879678d33d7501d388f5a446a8c7de147df7","size":60,"type":"InvocationTransaction","version":1,"attributes":[],"vin":[],"vout":[],"scripts":[],"script":"097465737476616c756507746573746b657952c103507574676f20ccfbd5f01d5b9633387428b8bab95a9e78c2"}]}]}`, `{"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 e021cc64a..b1cadb605 100644 --- a/pkg/rpc/response/result/block.go +++ b/pkg/rpc/response/result/block.go @@ -3,12 +3,10 @@ package result import ( "encoding/json" "errors" - "fmt" "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" "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" ) @@ -30,42 +28,29 @@ type ( // 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 uint32 `json:"time"` - Index uint32 `json:"index"` - Nonce string `json:"nonce"` - NextConsensus string `json:"nextconsensus"` + *block.Base + BlockMetadataAndTx + } - Confirmations uint32 `json:"confirmations"` - - Script transaction.Witness `json:"script"` - - Tx []Tx `json:"tx"` + // BlockMetadataAndTx is an additional metadata added to standard + // block.Base plus specially encoded transactions. + BlockMetadataAndTx struct { + Size int `json:"size"` + NextBlockHash *util.Uint256 `json:"nextblockhash,omitempty"` + Confirmations uint32 `json:"confirmations"` + Tx []Tx `json:"tx"` } ) // NewBlock creates a new Block wrapper. func NewBlock(b *block.Block, chain core.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, - Nonce: fmt.Sprintf("%016x", b.ConsensusData), - NextConsensus: address.Uint160ToString(b.NextConsensus), - Confirmations: chain.BlockHeight() - b.Index - 1, - - Script: b.Script, - - Tx: make([]Tx, 0, len(b.Transactions)), + Base: &b.Base, + BlockMetadataAndTx: BlockMetadataAndTx{ + Size: io.GetVarSize(b), + Confirmations: chain.BlockHeight() - b.Index - 1, + Tx: make([]Tx, 0, len(b.Transactions)), + }, } hash := chain.GetHeaderHash(int(b.Index) + 1) @@ -130,3 +115,44 @@ func (t *Tx) UnmarshalJSON(data []byte) error { t.Transaction = transaction return nil } + +// MarshalJSON implements json.Marshaler interface. +func (b Block) MarshalJSON() ([]byte, error) { + output, err := json.Marshal(b.BlockMetadataAndTx) + if err != nil { + return nil, err + } + baseBytes, err := json.Marshal(b.Base) + 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.Base and BlockMetadataAndTx are at the same level in json, + // do unmarshalling separately for both structs. + metaTx := new(BlockMetadataAndTx) + base := new(block.Base) + err := json.Unmarshal(data, metaTx) + if err != nil { + return err + } + err = json.Unmarshal(data, base) + if err != nil { + return err + } + b.Base = base + b.BlockMetadataAndTx = *metaTx + return nil +} diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 058e724f1..6103156f7 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -336,7 +336,7 @@ var rpcTestCases = map[string][]rpcTestCase{ block, err := e.chain.GetBlock(e.chain.GetHeaderHash(2)) require.NoErrorf(t, err, "could not get block") - assert.Equal(t, block.Hash(), res.Hash) + assert.Equal(t, block.Hash(), res.Hash()) for i := range res.Tx { tx := res.Tx[i] require.Equal(t, transaction.MinerType, tx.Transaction.Type) diff --git a/pkg/rpc/server/subscription_test.go b/pkg/rpc/server/subscription_test.go index 8d494421b..c2afb6639 100644 --- a/pkg/rpc/server/subscription_test.go +++ b/pkg/rpc/server/subscription_test.go @@ -199,7 +199,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 }