diff --git a/client/rpc/block.go b/client/rpc/block.go index 6e3ea231f409..a637e6552786 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -68,7 +68,7 @@ func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) { return codec.Cdc.MarshalJSONIndent(res, "", " ") } - return codec.Cdc.MarshalJSON(res) + return codec.Cdc.MarshalJSON(ConvertBlockResult(res)) } // get the current blockchain height diff --git a/client/rpc/block_converter.go b/client/rpc/block_converter.go new file mode 100644 index 000000000000..6edf3f81d815 --- /dev/null +++ b/client/rpc/block_converter.go @@ -0,0 +1,221 @@ +package rpc + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" +) + +// ResultBlock represents a single block with metadata +type ResultBlock struct { + BlockMeta BlockMeta `json:"block_meta"` + Block Block `json:"block"` +} + +// BlockMeta contains meta information about a block - namely, it's ID and Header. +type BlockMeta struct { + BlockID types.BlockID `json:"block_id"` // the block hash and partsethash + Header Header `json:"header"` // The block's Header +} + +// Header defines a wrapper around Tendermint's Header type overriding various fields. +// nolint: structtag +type Header struct { + // embed original type + types.Header + + // override fields so json.Marshal will marshal in accordance with amino JSON format + Version Consensus `json:"version"` + Height int64 `json:"height,string"` + NumTxs int64 `json:"num_txs,string"` + TotalTxs int64 `json:"total_txs,string"` + LastBlockID BlockID `json:"last_block_id"` + ProposerAddress sdk.ValAddress `json:"proposer_address"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (h Header) MarshalJSON() ([]byte, error) { + type headerJSON Header + return json.Marshal(headerJSON(h)) +} + +// BlockID defines a wrapper around Tendermint's BlockID type overriding various fields. +// nolint: structtag +type BlockID struct { + // embed original type + types.BlockID + + // override fields from original type + PartsHeader PartSetHeader `json:"parts"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (b BlockID) MarshalJSON() ([]byte, error) { + type blockIDJSON BlockID + return json.Marshal(blockIDJSON(b)) +} + +// PartSetHeader defines a wrapper around Tendermint's PartSetHeader type overriding various fields. +// nolint: structtag +type PartSetHeader struct { + // embed original type + types.PartSetHeader + + // override fields from original type + Total int `json:"total,string"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (b PartSetHeader) MarshalJSON() ([]byte, error) { + type partSetHeadJSON PartSetHeader + return json.Marshal(partSetHeadJSON(b)) +} + +// Consensus defines a wrapper around Tendermint's Consensus type overriding various fields. +// nolint: structtag +type Consensus struct { + // embed original type + version.Consensus + + // override fields so json.Marshal will marshal in accordance with amino JSON format + App uint64 `json:"app,string"` + Block uint64 `json:"block,string"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (b Consensus) MarshalJSON() ([]byte, error) { + type consensusJSON Consensus + return json.Marshal(consensusJSON(b)) +} + +// Block defines the atomic unit of a Tendermint blockchain. +// nolint: structtag. +type Block struct { + // embed original type + types.Block + + // override fields + Header Header `json:"header"` + LastCommit Commit `json:"last_commit"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (b Block) MarshalJSON() ([]byte, error) { + type blockJSON Block + return json.Marshal(blockJSON(b)) +} + +// Commit defines a wrapper around Tendermint's Commit type overriding various fields. +// nolint: structtag. +type Commit struct { + // embed original type + *types.Commit + + // override fields + BlockID BlockID `json:"block_id"` + Precommits []CommitSig `json:"precommits"` +} + +// CommitSig defines a wrapper around Tendermint's CommitSig type overriding various fields. +// nolint: structtag +type CommitSig struct { + // embed original type + types.CommitSig + + // override fields so json.Marshal will marshal in accordance with amino JSON format + Height int64 `json:"height,string"` + Round int `json:"round,string"` + BlockID BlockID `json:"block_id"` + ValidatorAddress sdk.ValAddress `json:"validator_address"` + ValidatorIndex int `json:"validator_index,string"` +} + +// MarshalJSON implements the json.Marshaler interface. We do this because Amino +// does not respect the JSON stdlib embedding semantics. +func (c CommitSig) MarshalJSON() ([]byte, error) { + type commitSigJSON CommitSig + return json.Marshal(commitSigJSON(c)) +} + +// ConvertBlockResult allows to convert the given standard ResultBlock into a new ResultBlock having all the +// validator addresses as Bech32 strings instead of HEX ones. +func ConvertBlockResult(res *ctypes.ResultBlock) (blockResult *ResultBlock) { + + if res == nil { + return nil + } + + header := Header{ + Header: res.BlockMeta.Header, + + Version: Consensus{ + Consensus: res.BlockMeta.Header.Version, + App: res.BlockMeta.Header.Version.App.Uint64(), + Block: res.BlockMeta.Header.Version.Block.Uint64(), + }, + Height: res.BlockMeta.Header.Height, + NumTxs: res.BlockMeta.Header.NumTxs, + TotalTxs: res.BlockMeta.Header.TotalTxs, + LastBlockID: BlockID{ + BlockID: res.BlockMeta.Header.LastBlockID, + PartsHeader: PartSetHeader{ + PartSetHeader: res.BlockMeta.Header.LastBlockID.PartsHeader, + Total: res.BlockMeta.Header.LastBlockID.PartsHeader.Total, + }, + }, + + ProposerAddress: sdk.ValAddress(res.BlockMeta.Header.ProposerAddress), + } + + return &ResultBlock{ + BlockMeta: BlockMeta{ + BlockID: res.BlockMeta.BlockID, + Header: header, + }, + Block: Block{ + Header: header, + LastCommit: Commit{ + Commit: res.Block.LastCommit, + BlockID: BlockID{ + BlockID: res.Block.LastCommit.BlockID, + PartsHeader: PartSetHeader{ + PartSetHeader: res.Block.LastCommit.BlockID.PartsHeader, + Total: res.Block.LastCommit.BlockID.PartsHeader.Total, + }, + }, + Precommits: convertPreCommits(res.Block.LastCommit.Precommits), + }, + }, + } +} + +func convertPreCommits(preCommits []*types.CommitSig) (sigs []CommitSig) { + for _, commit := range preCommits { + sig := CommitSig{ + CommitSig: *commit, + Height: commit.Height, + Round: commit.Round, + BlockID: BlockID{ + BlockID: commit.BlockID, + PartsHeader: PartSetHeader{ + PartSetHeader: commit.BlockID.PartsHeader, + Total: commit.BlockID.PartsHeader.Total, + }, + }, + ValidatorAddress: sdk.ValAddress(commit.ValidatorAddress), + ValidatorIndex: commit.ValidatorIndex, + } + + sigs = append(sigs, sig) + } + + return sigs +} diff --git a/client/rpc/block_converter_test.go b/client/rpc/block_converter_test.go new file mode 100644 index 000000000000..50d4d7f65b90 --- /dev/null +++ b/client/rpc/block_converter_test.go @@ -0,0 +1,27 @@ +package rpc + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/stretchr/testify/assert" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +func TestConvertBlockResult(t *testing.T) { + cdc := codec.New() + + // test when the block is not nil + var block ctypes.ResultBlock + defaultBlockJSON := `{"block_meta":{"block_id":{"hash":"30CD3A9EF2082FF9F2575655E99E37ACC3936DC9E534ADF0BD7436C76258225C","parts":{"total":"1","hash":"463460DA73FA0AE441B6041BF2683AE625CE2D9FD290F4C86CD83D1A7FB9F439"}},"header":{"version":{"block":"10","app":"0"},"chain_id":"test-chain-RoCfX4","height":"16","time":"2019-10-16T09:47:21.054275229Z","num_txs":"0","total_txs":"0","last_block_id":{"hash":"168D140232DA3A59757658E257168B6BCE62024F574CA222C2D449BAB1AE4023","parts":{"total":"1","hash":"4E6D7A1C7DE6942470394C21B132A2D9F89B31908C3B6FA8C2AB96A87553B96E"}},"last_commit_hash":"3483509FCC8048496E9B85E1E4C90A3B6E5944F022FE3C0709096932A010F712","data_hash":"","validators_hash":"61E69204249E5EE6F777C0566040929276A1900CE665BCB11006CABF9A4ED9A3","next_validators_hash":"61E69204249E5EE6F777C0566040929276A1900CE665BCB11006CABF9A4ED9A3","consensus_hash":"048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F","app_hash":"070A8213F67F494F7D27A6296C29F0DF62DFA18FD37FB592D9761A44DEE96528","last_results_hash":"","evidence_hash":"","proposer_address":"E8B4AF895B301C3D7108CA93A0B9234E3A2A4B21"}},"block":{"header":{"version":{"block":"10","app":"0"},"chain_id":"test-chain-RoCfX4","height":"16","time":"2019-10-16T09:47:21.054275229Z","num_txs":"0","total_txs":"0","last_block_id":{"hash":"168D140232DA3A59757658E257168B6BCE62024F574CA222C2D449BAB1AE4023","parts":{"total":"1","hash":"4E6D7A1C7DE6942470394C21B132A2D9F89B31908C3B6FA8C2AB96A87553B96E"}},"last_commit_hash":"3483509FCC8048496E9B85E1E4C90A3B6E5944F022FE3C0709096932A010F712","data_hash":"","validators_hash":"61E69204249E5EE6F777C0566040929276A1900CE665BCB11006CABF9A4ED9A3","next_validators_hash":"61E69204249E5EE6F777C0566040929276A1900CE665BCB11006CABF9A4ED9A3","consensus_hash":"048091BC7DDC283F77BFBF91D73C44DA58C3DF8A9CBC867405D8B7F3DAADA22F","app_hash":"070A8213F67F494F7D27A6296C29F0DF62DFA18FD37FB592D9761A44DEE96528","last_results_hash":"","evidence_hash":"","proposer_address":"E8B4AF895B301C3D7108CA93A0B9234E3A2A4B21"},"data":{"txs":null},"evidence":{"evidence":null},"last_commit":{"block_id":{"hash":"168D140232DA3A59757658E257168B6BCE62024F574CA222C2D449BAB1AE4023","parts":{"total":"1","hash":"4E6D7A1C7DE6942470394C21B132A2D9F89B31908C3B6FA8C2AB96A87553B96E"}},"precommits":[{"type":2,"height":"15","round":"0","block_id":{"hash":"168D140232DA3A59757658E257168B6BCE62024F574CA222C2D449BAB1AE4023","parts":{"total":"1","hash":"4E6D7A1C7DE6942470394C21B132A2D9F89B31908C3B6FA8C2AB96A87553B96E"}},"timestamp":"2019-10-16T09:47:21.054275229Z","validator_address":"E8B4AF895B301C3D7108CA93A0B9234E3A2A4B21","validator_index":"0","signature":"eXB1NPmarUC5+2SCUQiWUCoGqtq0nrSn7giMAF/zWbfv3KmueE+1NoZ0CPLJAhBodz8Y8oL8xVy6ElA7J72kBw=="}]}}}` + cdc.MustUnmarshalJSON([]byte(defaultBlockJSON), &block) + + convertedBlock := ConvertBlockResult(&block) + assert.Equal(t, "cosmosvaloper1az62lz2mxqwr6ugge2f6pwfrfcaz5jephqhvkt", convertedBlock.BlockMeta.Header.ProposerAddress.String()) + + convertedBlockString := string(cdc.MustMarshalJSON(&convertedBlock)) + assert.NotContains(t, "E8B4AF895B301C3D7108CA93A0B9234E3A2A4B21", convertedBlockString) + + // test when the block is nil + assert.Nil(t, ConvertBlockResult(nil)) +}