diff --git a/abci/types/types.go b/abci/types/types.go index d13947d1a..121e72159 100644 --- a/abci/types/types.go +++ b/abci/types/types.go @@ -5,6 +5,9 @@ import ( "encoding/json" "github.com/gogo/protobuf/jsonpb" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/encoding" + "github.com/tendermint/tendermint/internal/jsontypes" ) const ( @@ -135,6 +138,48 @@ func (r *EventAttribute) UnmarshalJSON(b []byte) error { return jsonpbUnmarshaller.Unmarshal(reader, r) } +// validatorUpdateJSON is the JSON encoding of a validator update. +// +// It handles translation of public keys from the protobuf representation to +// the legacy Amino-compatible format expected by RPC clients. +type validatorUpdateJSON struct { + PubKey json.RawMessage `json:"pub_key,omitempty"` + Power int64 `json:"power,string"` +} + +func (v *ValidatorUpdate) MarshalJSON() ([]byte, error) { + key, err := encoding.PubKeyFromProto(v.PubKey) + if err != nil { + return nil, err + } + jkey, err := jsontypes.Marshal(key) + if err != nil { + return nil, err + } + return json.Marshal(validatorUpdateJSON{ + PubKey: jkey, + Power: v.GetPower(), + }) +} + +func (v *ValidatorUpdate) UnmarshalJSON(data []byte) error { + var vu validatorUpdateJSON + if err := json.Unmarshal(data, &vu); err != nil { + return err + } + var key crypto.PubKey + if err := jsontypes.Unmarshal(vu.PubKey, &key); err != nil { + return err + } + pkey, err := encoding.PubKeyToProto(key) + if err != nil { + return err + } + v.PubKey = pkey + v.Power = vu.Power + return nil +} + // Some compile time assertions to ensure we don't // have accidental runtime surprises later on. diff --git a/rpc/coretypes/responses_test.go b/rpc/coretypes/responses_test.go index d4ced795a..bf66db0c9 100644 --- a/rpc/coretypes/responses_test.go +++ b/rpc/coretypes/responses_test.go @@ -1,10 +1,18 @@ package coretypes import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" "testing" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + pbcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" "github.com/tendermint/tendermint/types" ) @@ -33,3 +41,53 @@ func TestStatusIndexer(t *testing.T) { assert.Equal(t, tc.expected, status.TxIndexEnabled()) } } + +// A regression test for https://github.com/tendermint/tendermint/issues/8583. +func TestResultBlockResults_regression8583(t *testing.T) { + const keyData = "0123456789abcdef0123456789abcdef" // 32 bytes + wantKey := base64.StdEncoding.EncodeToString([]byte(keyData)) + + rsp := &ResultBlockResults{ + ValidatorUpdates: []abci.ValidatorUpdate{{ + PubKey: pbcrypto.PublicKey{ + Sum: &pbcrypto.PublicKey_Ed25519{Ed25519: []byte(keyData)}, + }, + Power: 400, + }}, + } + + // Use compact here so the test data remain legible. The output from the + // marshaler will have whitespace folded out so we need to do that too for + // the comparison to be valid. + var buf bytes.Buffer + require.NoError(t, json.Compact(&buf, []byte(fmt.Sprintf(` +{ + "height": "0", + "txs_results": null, + "total_gas_used": "0", + "finalize_block_events": null, + "validator_updates": [ + { + "pub_key":{"type": "tendermint/PubKeyEd25519", "value": "%s"}, + "power": "400" + } + ], + "consensus_param_updates": null +}`, wantKey)))) + + bits, err := json.Marshal(rsp) + if err != nil { + t.Fatalf("Encoding block result: %v", err) + } + if diff := cmp.Diff(buf.String(), string(bits)); diff != "" { + t.Errorf("Marshaled result (-want, +got):\n%s", diff) + } + + back := new(ResultBlockResults) + if err := json.Unmarshal(bits, back); err != nil { + t.Fatalf("Unmarshaling: %v", err) + } + if diff := cmp.Diff(rsp, back); diff != "" { + t.Errorf("Unmarshaled result (-want, +got):\n%s", diff) + } +}