rpc/block: rework the way Block is JSONized

Our block.Block was JSONized in a bit different fashion than result.Block in
its Nonce and NextConsensus 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 in result 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.Base which is
to be serialized in proper way.
This commit is contained in:
Roman Khimov 2020-05-13 21:27:08 +03:00
parent 83febead59
commit 9546e021a9
8 changed files with 267 additions and 94 deletions

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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,
},
}},
},
}
},
},

View file

@ -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) {

View file

@ -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
}

View file

@ -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)

View file

@ -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
}