From 7878ca6a8a358823984f20c26dcd19d93cace4c1 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 24 Jan 2022 08:15:34 -0800 Subject: [PATCH] Delete the custom libs/json (tmjson) package. (#7673) There are no further uses of this package anywhere in Tendermint. All the uses in the Cosmos SDK are for types that now work correctly with the standard encoding/json package. --- CHANGELOG_PENDING.md | 1 + crypto/encoding/codec.go | 8 +- crypto/merkle/proof.go | 8 +- internal/consensus/msgs.go | 56 +++-- internal/consensus/state.go | 38 +++- internal/consensus/types/round_state.go | 2 +- internal/consensus/wal.go | 12 +- libs/json/decoder.go | 278 ------------------------ libs/json/decoder_test.go | 151 ------------- libs/json/doc.go | 99 --------- libs/json/encoder.go | 254 ---------------------- libs/json/encoder_test.go | 104 --------- libs/json/helpers_test.go | 91 -------- libs/json/structs.go | 87 -------- libs/json/types.go | 108 --------- light/mbt/driver_test.go | 6 +- proto/tendermint/crypto/crypto.go | 7 + scripts/json2wal/main.go | 4 +- scripts/wal2json/main.go | 4 +- types/proposal.go | 2 +- 20 files changed, 104 insertions(+), 1216 deletions(-) delete mode 100644 libs/json/decoder.go delete mode 100644 libs/json/decoder_test.go delete mode 100644 libs/json/doc.go delete mode 100644 libs/json/encoder.go delete mode 100644 libs/json/encoder_test.go delete mode 100644 libs/json/helpers_test.go delete mode 100644 libs/json/structs.go delete mode 100644 libs/json/types.go create mode 100644 proto/tendermint/crypto/crypto.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 4d563d742..8b130aa00 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -40,6 +40,7 @@ Special thanks to external contributors on this release: - [config] \#7169 `WriteConfigFile` now returns an error. (@tychoish) - [libs/service] \#7288 Remove SetLogger method on `service.Service` interface. (@tychoish) - [abci/client] \#7607 Simplify client interface (removes most "async" methods). (@creachadair) + - [libs/json] \#7673 Remove the libs/json (tmjson) library. (@creachadair) - Blockchain Protocol diff --git a/crypto/encoding/codec.go b/crypto/encoding/codec.go index 37249bcb3..fd32f101c 100644 --- a/crypto/encoding/codec.go +++ b/crypto/encoding/codec.go @@ -7,14 +7,14 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/crypto/sr25519" - "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/internal/jsontypes" cryptoproto "github.com/tendermint/tendermint/proto/tendermint/crypto" ) func init() { - json.RegisterType((*cryptoproto.PublicKey)(nil), "tendermint.crypto.PublicKey") - json.RegisterType((*cryptoproto.PublicKey_Ed25519)(nil), "tendermint.crypto.PublicKey_Ed25519") - json.RegisterType((*cryptoproto.PublicKey_Secp256K1)(nil), "tendermint.crypto.PublicKey_Secp256K1") + jsontypes.MustRegister((*cryptoproto.PublicKey)(nil)) + jsontypes.MustRegister((*cryptoproto.PublicKey_Ed25519)(nil)) + jsontypes.MustRegister((*cryptoproto.PublicKey_Secp256K1)(nil)) } // PubKeyToProto takes crypto.PubKey and transforms it to a protobuf Pubkey diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 80b289d23..4f09e4414 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -24,10 +24,10 @@ const ( // everything. This also affects the generalized proof system as // well. type Proof struct { - Total int64 `json:"total"` // Total number of items. - Index int64 `json:"index"` // Index of item to prove. - LeafHash []byte `json:"leaf_hash"` // Hash of item value. - Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. + Total int64 `json:"total,string"` // Total number of items. + Index int64 `json:"index,string"` // Index of item to prove. + LeafHash []byte `json:"leaf_hash"` // Hash of item value. + Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. } // ProofsFromByteSlices computes inclusion proof for given items. diff --git a/internal/consensus/msgs.go b/internal/consensus/msgs.go index 72539b783..8b19db423 100644 --- a/internal/consensus/msgs.go +++ b/internal/consensus/msgs.go @@ -5,8 +5,8 @@ import ( "fmt" cstypes "github.com/tendermint/tendermint/internal/consensus/types" + "github.com/tendermint/tendermint/internal/jsontypes" "github.com/tendermint/tendermint/libs/bits" - tmjson "github.com/tendermint/tendermint/libs/json" tmmath "github.com/tendermint/tendermint/libs/math" tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -18,30 +18,34 @@ import ( // converted to a Message via MsgFromProto. type Message interface { ValidateBasic() error + + jsontypes.Tagged } func init() { - tmjson.RegisterType(&NewRoundStepMessage{}, "tendermint/NewRoundStepMessage") - tmjson.RegisterType(&NewValidBlockMessage{}, "tendermint/NewValidBlockMessage") - tmjson.RegisterType(&ProposalMessage{}, "tendermint/Proposal") - tmjson.RegisterType(&ProposalPOLMessage{}, "tendermint/ProposalPOL") - tmjson.RegisterType(&BlockPartMessage{}, "tendermint/BlockPart") - tmjson.RegisterType(&VoteMessage{}, "tendermint/Vote") - tmjson.RegisterType(&HasVoteMessage{}, "tendermint/HasVote") - tmjson.RegisterType(&VoteSetMaj23Message{}, "tendermint/VoteSetMaj23") - tmjson.RegisterType(&VoteSetBitsMessage{}, "tendermint/VoteSetBits") + jsontypes.MustRegister(&NewRoundStepMessage{}) + jsontypes.MustRegister(&NewValidBlockMessage{}) + jsontypes.MustRegister(&ProposalMessage{}) + jsontypes.MustRegister(&ProposalPOLMessage{}) + jsontypes.MustRegister(&BlockPartMessage{}) + jsontypes.MustRegister(&VoteMessage{}) + jsontypes.MustRegister(&HasVoteMessage{}) + jsontypes.MustRegister(&VoteSetMaj23Message{}) + jsontypes.MustRegister(&VoteSetBitsMessage{}) } // NewRoundStepMessage is sent for every step taken in the ConsensusState. // For every height/round/step transition type NewRoundStepMessage struct { - Height int64 + Height int64 `json:",string"` Round int32 Step cstypes.RoundStepType - SecondsSinceStartTime int64 + SecondsSinceStartTime int64 `json:",string"` LastCommitRound int32 } +func (*NewRoundStepMessage) TypeTag() string { return "tendermint/NewRoundStepMessage" } + // ValidateBasic performs basic validation. func (m *NewRoundStepMessage) ValidateBasic() error { if m.Height < 0 { @@ -93,13 +97,15 @@ func (m *NewRoundStepMessage) String() string { // i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. // In case the block is also committed, then IsCommit flag is set to true. type NewValidBlockMessage struct { - Height int64 + Height int64 `json:",string"` Round int32 BlockPartSetHeader types.PartSetHeader BlockParts *bits.BitArray IsCommit bool } +func (*NewValidBlockMessage) TypeTag() string { return "tendermint/NewValidBlockMessage" } + // ValidateBasic performs basic validation. func (m *NewValidBlockMessage) ValidateBasic() error { if m.Height < 0 { @@ -136,6 +142,8 @@ type ProposalMessage struct { Proposal *types.Proposal } +func (*ProposalMessage) TypeTag() string { return "tendermint/Proposal" } + // ValidateBasic performs basic validation. func (m *ProposalMessage) ValidateBasic() error { return m.Proposal.ValidateBasic() @@ -148,11 +156,13 @@ func (m *ProposalMessage) String() string { // ProposalPOLMessage is sent when a previous proposal is re-proposed. type ProposalPOLMessage struct { - Height int64 + Height int64 `json:",string"` ProposalPOLRound int32 ProposalPOL *bits.BitArray } +func (*ProposalPOLMessage) TypeTag() string { return "tendermint/ProposalPOL" } + // ValidateBasic performs basic validation. func (m *ProposalPOLMessage) ValidateBasic() error { if m.Height < 0 { @@ -177,11 +187,13 @@ func (m *ProposalPOLMessage) String() string { // BlockPartMessage is sent when gossipping a piece of the proposed block. type BlockPartMessage struct { - Height int64 + Height int64 `json:",string"` Round int32 Part *types.Part } +func (*BlockPartMessage) TypeTag() string { return "tendermint/BlockPart" } + // ValidateBasic performs basic validation. func (m *BlockPartMessage) ValidateBasic() error { if m.Height < 0 { @@ -206,6 +218,8 @@ type VoteMessage struct { Vote *types.Vote } +func (*VoteMessage) TypeTag() string { return "tendermint/Vote" } + // ValidateBasic performs basic validation. func (m *VoteMessage) ValidateBasic() error { return m.Vote.ValidateBasic() @@ -218,12 +232,14 @@ func (m *VoteMessage) String() string { // HasVoteMessage is sent to indicate that a particular vote has been received. type HasVoteMessage struct { - Height int64 + Height int64 `json:",string"` Round int32 Type tmproto.SignedMsgType Index int32 } +func (*HasVoteMessage) TypeTag() string { return "tendermint/HasVote" } + // ValidateBasic performs basic validation. func (m *HasVoteMessage) ValidateBasic() error { if m.Height < 0 { @@ -248,12 +264,14 @@ func (m *HasVoteMessage) String() string { // VoteSetMaj23Message is sent to indicate that a given BlockID has seen +2/3 votes. type VoteSetMaj23Message struct { - Height int64 + Height int64 `json:",string"` Round int32 Type tmproto.SignedMsgType BlockID types.BlockID } +func (*VoteSetMaj23Message) TypeTag() string { return "tendermint/VoteSetMaj23" } + // ValidateBasic performs basic validation. func (m *VoteSetMaj23Message) ValidateBasic() error { if m.Height < 0 { @@ -280,13 +298,15 @@ func (m *VoteSetMaj23Message) String() string { // VoteSetBitsMessage is sent to communicate the bit-array of votes seen for the // BlockID. type VoteSetBitsMessage struct { - Height int64 + Height int64 `json:",string"` Round int32 Type tmproto.SignedMsgType BlockID types.BlockID Votes *bits.BitArray } +func (*VoteSetBitsMessage) TypeTag() string { return "tendermint/VoteSetBits" } + // ValidateBasic performs basic validation. func (m *VoteSetBitsMessage) ValidateBasic() error { if m.Height < 0 { diff --git a/internal/consensus/state.go b/internal/consensus/state.go index eb4b6b2c6..f06a658e1 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -19,6 +19,7 @@ import ( "github.com/tendermint/tendermint/crypto" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/eventbus" + "github.com/tendermint/tendermint/internal/jsontypes" sm "github.com/tendermint/tendermint/internal/state" tmevents "github.com/tendermint/tendermint/libs/events" "github.com/tendermint/tendermint/libs/log" @@ -46,18 +47,47 @@ var msgQueueSize = 1000 // msgs from the reactor which may update the state type msgInfo struct { - Msg Message `json:"msg"` - PeerID types.NodeID `json:"peer_key"` + Msg Message + PeerID types.NodeID +} + +func (msgInfo) TypeTag() string { return "tendermint/wal/MsgInfo" } + +type msgInfoJSON struct { + Msg json.RawMessage `json:"msg"` + PeerID types.NodeID `json:"peer_key"` +} + +func (m msgInfo) MarshalJSON() ([]byte, error) { + msg, err := jsontypes.Marshal(m.Msg) + if err != nil { + return nil, err + } + return json.Marshal(msgInfoJSON{Msg: msg, PeerID: m.PeerID}) +} + +func (m *msgInfo) UnmarshalJSON(data []byte) error { + var msg msgInfoJSON + if err := json.Unmarshal(data, &msg); err != nil { + return err + } + if err := jsontypes.Unmarshal(msg.Msg, &m.Msg); err != nil { + return err + } + m.PeerID = msg.PeerID + return nil } // internally generated messages which may update the state type timeoutInfo struct { - Duration time.Duration `json:"duration"` - Height int64 `json:"height"` + Duration time.Duration `json:"duration,string"` + Height int64 `json:"height,string"` Round int32 `json:"round"` Step cstypes.RoundStepType `json:"step"` } +func (timeoutInfo) TypeTag() string { return "tendermint/wal/TimeoutInfo" } + func (ti *timeoutInfo) String() string { return fmt.Sprintf("%v ; %d/%d %v", ti.Duration, ti.Height, ti.Round, ti.Step) } diff --git a/internal/consensus/types/round_state.go b/internal/consensus/types/round_state.go index 9e67b76c0..15f1777b4 100644 --- a/internal/consensus/types/round_state.go +++ b/internal/consensus/types/round_state.go @@ -65,7 +65,7 @@ func (rs RoundStepType) String() string { // NOTE: Not thread safe. Should only be manipulated by functions downstream // of the cs.receiveRoutine type RoundState struct { - Height int64 `json:"height"` // Height we are working on + Height int64 `json:"height,string"` // Height we are working on Round int32 `json:"round"` Step RoundStepType `json:"step"` StartTime time.Time `json:"start_time"` diff --git a/internal/consensus/wal.go b/internal/consensus/wal.go index 36993e762..92d7a6b82 100644 --- a/internal/consensus/wal.go +++ b/internal/consensus/wal.go @@ -12,8 +12,8 @@ import ( "github.com/gogo/protobuf/proto" + "github.com/tendermint/tendermint/internal/jsontypes" auto "github.com/tendermint/tendermint/internal/libs/autofile" - tmjson "github.com/tendermint/tendermint/libs/json" "github.com/tendermint/tendermint/libs/log" tmos "github.com/tendermint/tendermint/libs/os" "github.com/tendermint/tendermint/libs/service" @@ -41,15 +41,17 @@ type TimedWALMessage struct { // EndHeightMessage marks the end of the given height inside WAL. // @internal used by scripts/wal2json util. type EndHeightMessage struct { - Height int64 `json:"height"` + Height int64 `json:"height,string"` } +func (EndHeightMessage) TypeTag() string { return "tendermint/wal/EndHeightMessage" } + type WALMessage interface{} func init() { - tmjson.RegisterType(msgInfo{}, "tendermint/wal/MsgInfo") - tmjson.RegisterType(timeoutInfo{}, "tendermint/wal/TimeoutInfo") - tmjson.RegisterType(EndHeightMessage{}, "tendermint/wal/EndHeightMessage") + jsontypes.MustRegister(msgInfo{}) + jsontypes.MustRegister(timeoutInfo{}) + jsontypes.MustRegister(EndHeightMessage{}) } //-------------------------------------------------------- diff --git a/libs/json/decoder.go b/libs/json/decoder.go deleted file mode 100644 index 86ff27d39..000000000 --- a/libs/json/decoder.go +++ /dev/null @@ -1,278 +0,0 @@ -package json - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "reflect" -) - -// Unmarshal unmarshals JSON into the given value, using Amino-compatible JSON encoding (strings -// for 64-bit numbers, and type wrappers for registered types). -func Unmarshal(bz []byte, v interface{}) error { - return decode(bz, v) -} - -func decode(bz []byte, v interface{}) error { - if len(bz) == 0 { - return errors.New("cannot decode empty bytes") - } - - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr { - return errors.New("must decode into a pointer") - } - rv = rv.Elem() - - // If this is a registered type, defer to interface decoder regardless of whether the input is - // an interface or a bare value. This retains Amino's behavior, but is inconsistent with - // behavior in structs where an interface field will get the type wrapper while a bare value - // field will not. - if typeRegistry.name(rv.Type()) != "" { - return decodeReflectInterface(bz, rv) - } - - return decodeReflect(bz, rv) -} - -func decodeReflect(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() { - return errors.New("value is not addressable") - } - - // Handle null for slices, interfaces, and pointers - if bytes.Equal(bz, []byte("null")) { - rv.Set(reflect.Zero(rv.Type())) - return nil - } - - // Dereference-and-construct pointers, to handle nested pointers. - for rv.Kind() == reflect.Ptr { - if rv.IsNil() { - rv.Set(reflect.New(rv.Type().Elem())) - } - rv = rv.Elem() - } - - // Times must be UTC and end with Z - if rv.Type() == timeType { - switch { - case len(bz) < 2 || bz[0] != '"' || bz[len(bz)-1] != '"': - return fmt.Errorf("JSON time must be an RFC3339 string, but got %q", bz) - case bz[len(bz)-2] != 'Z': - return fmt.Errorf("JSON time must be UTC and end with 'Z', but got %q", bz) - } - } - - // If value implements json.Umarshaler, call it. - if rv.Addr().Type().Implements(jsonUnmarshalerType) { - return rv.Addr().Interface().(json.Unmarshaler).UnmarshalJSON(bz) - } - - switch rv.Type().Kind() { - // Decode complex types recursively. - case reflect.Slice, reflect.Array: - return decodeReflectList(bz, rv) - - case reflect.Map: - return decodeReflectMap(bz, rv) - - case reflect.Struct: - return decodeReflectStruct(bz, rv) - - case reflect.Interface: - return decodeReflectInterface(bz, rv) - - // For 64-bit integers, unwrap expected string and defer to stdlib for integer decoding. - case reflect.Int64, reflect.Int, reflect.Uint64, reflect.Uint: - if bz[0] != '"' || bz[len(bz)-1] != '"' { - return fmt.Errorf("invalid 64-bit integer encoding %q, expected string", string(bz)) - } - bz = bz[1 : len(bz)-1] - fallthrough - - // Anything else we defer to the stdlib. - default: - return decodeStdlib(bz, rv) - } -} - -func decodeReflectList(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() { - return errors.New("list value is not addressable") - } - - switch rv.Type().Elem().Kind() { - // Decode base64-encoded bytes using stdlib decoder, via byte slice for arrays. - case reflect.Uint8: - if rv.Type().Kind() == reflect.Array { - var buf []byte - if err := json.Unmarshal(bz, &buf); err != nil { - return err - } - if len(buf) != rv.Len() { - return fmt.Errorf("got %v bytes, expected %v", len(buf), rv.Len()) - } - reflect.Copy(rv, reflect.ValueOf(buf)) - - } else if err := decodeStdlib(bz, rv); err != nil { - return err - } - - // Decode anything else into a raw JSON slice, and decode values recursively. - default: - var rawSlice []json.RawMessage - if err := json.Unmarshal(bz, &rawSlice); err != nil { - return err - } - if rv.Type().Kind() == reflect.Slice { - rv.Set(reflect.MakeSlice(reflect.SliceOf(rv.Type().Elem()), len(rawSlice), len(rawSlice))) - } - if rv.Len() != len(rawSlice) { // arrays of wrong size - return fmt.Errorf("got list of %v elements, expected %v", len(rawSlice), rv.Len()) - } - for i, bz := range rawSlice { - if err := decodeReflect(bz, rv.Index(i)); err != nil { - return err - } - } - } - - // Replace empty slices with nil slices, for Amino compatibility - if rv.Type().Kind() == reflect.Slice && rv.Len() == 0 { - rv.Set(reflect.Zero(rv.Type())) - } - - return nil -} - -func decodeReflectMap(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() { - return errors.New("map value is not addressable") - } - - // Decode into a raw JSON map, using string keys. - rawMap := make(map[string]json.RawMessage) - if err := json.Unmarshal(bz, &rawMap); err != nil { - return err - } - if rv.Type().Key().Kind() != reflect.String { - return fmt.Errorf("map keys must be strings, got %v", rv.Type().Key().String()) - } - - // Recursively decode values. - rv.Set(reflect.MakeMapWithSize(rv.Type(), len(rawMap))) - for key, bz := range rawMap { - value := reflect.New(rv.Type().Elem()).Elem() - if err := decodeReflect(bz, value); err != nil { - return err - } - rv.SetMapIndex(reflect.ValueOf(key), value) - } - return nil -} - -func decodeReflectStruct(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() { - return errors.New("struct value is not addressable") - } - sInfo := makeStructInfo(rv.Type()) - - // Decode raw JSON values into a string-keyed map. - rawMap := make(map[string]json.RawMessage) - if err := json.Unmarshal(bz, &rawMap); err != nil { - return err - } - for i, fInfo := range sInfo.fields { - if !fInfo.hidden { - frv := rv.Field(i) - bz := rawMap[fInfo.jsonName] - if len(bz) > 0 { - if err := decodeReflect(bz, frv); err != nil { - return err - } - } else if !fInfo.omitEmpty { - frv.Set(reflect.Zero(frv.Type())) - } - } - } - - return nil -} - -func decodeReflectInterface(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() { - return errors.New("interface value not addressable") - } - - // Decode the interface wrapper. - wrapper := interfaceWrapper{} - if err := json.Unmarshal(bz, &wrapper); err != nil { - return err - } - if wrapper.Type == "" { - return errors.New("interface type cannot be empty") - } - if len(wrapper.Value) == 0 { - return errors.New("interface value cannot be empty") - } - - // Dereference-and-construct pointers, to handle nested pointers. - for rv.Kind() == reflect.Ptr { - if rv.IsNil() { - rv.Set(reflect.New(rv.Type().Elem())) - } - rv = rv.Elem() - } - - // Look up the interface type, and construct a concrete value. - rt, returnPtr := typeRegistry.lookup(wrapper.Type) - if rt == nil { - return fmt.Errorf("unknown type %q", wrapper.Type) - } - - cptr := reflect.New(rt) - crv := cptr.Elem() - if err := decodeReflect(wrapper.Value, crv); err != nil { - return err - } - - // This makes sure interface implementations with pointer receivers (e.g. func (c *Car)) are - // constructed as pointers behind the interface. The types must be registered as pointers with - // RegisterType(). - if rv.Type().Kind() == reflect.Interface && returnPtr { - if !cptr.Type().AssignableTo(rv.Type()) { - return fmt.Errorf("invalid type %q for this value", wrapper.Type) - } - rv.Set(cptr) - } else { - if !crv.Type().AssignableTo(rv.Type()) { - return fmt.Errorf("invalid type %q for this value", wrapper.Type) - } - rv.Set(crv) - } - return nil -} - -func decodeStdlib(bz []byte, rv reflect.Value) error { - if !rv.CanAddr() && rv.Kind() != reflect.Ptr { - return errors.New("value must be addressable or pointer") - } - - // Make sure we are unmarshaling into a pointer. - target := rv - if rv.Kind() != reflect.Ptr { - target = reflect.New(rv.Type()) - } - if err := json.Unmarshal(bz, target.Interface()); err != nil { - return err - } - rv.Set(target.Elem()) - return nil -} - -type interfaceWrapper struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} diff --git a/libs/json/decoder_test.go b/libs/json/decoder_test.go deleted file mode 100644 index 41faa1062..000000000 --- a/libs/json/decoder_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package json_test - -import ( - "reflect" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/libs/json" -) - -func TestUnmarshal(t *testing.T) { - i64Nil := (*int64)(nil) - str := "string" - strPtr := &str - structNil := (*Struct)(nil) - i32 := int32(32) - i64 := int64(64) - - testcases := map[string]struct { - json string - value interface{} - err bool - }{ - "bool true": {"true", true, false}, - "bool false": {"false", false, false}, - "float32": {"3.14", float32(3.14), false}, - "float64": {"3.14", float64(3.14), false}, - "int32": {`32`, int32(32), false}, - "int32 string": {`"32"`, int32(32), true}, - "int32 ptr": {`32`, &i32, false}, - "int64": {`"64"`, int64(64), false}, - "int64 noend": {`"64`, int64(64), true}, - "int64 number": {`64`, int64(64), true}, - "int64 ptr": {`"64"`, &i64, false}, - "int64 ptr nil": {`null`, i64Nil, false}, - "string": {`"foo"`, "foo", false}, - "string noend": {`"foo`, "foo", true}, - "string ptr": {`"string"`, &str, false}, - "slice byte": {`"AQID"`, []byte{1, 2, 3}, false}, - "slice bytes": {`["AQID"]`, [][]byte{{1, 2, 3}}, false}, - "slice int32": {`[1,2,3]`, []int32{1, 2, 3}, false}, - "slice int64": {`["1","2","3"]`, []int64{1, 2, 3}, false}, - "slice int64 number": {`[1,2,3]`, []int64{1, 2, 3}, true}, - "slice int64 ptr": {`["64"]`, []*int64{&i64}, false}, - "slice int64 empty": {`[]`, []int64(nil), false}, - "slice int64 null": {`null`, []int64(nil), false}, - "array byte": {`"AQID"`, [3]byte{1, 2, 3}, false}, - "array byte large": {`"AQID"`, [4]byte{1, 2, 3, 4}, true}, - "array byte small": {`"AQID"`, [2]byte{1, 2}, true}, - "array int32": {`[1,2,3]`, [3]int32{1, 2, 3}, false}, - "array int64": {`["1","2","3"]`, [3]int64{1, 2, 3}, false}, - "array int64 number": {`[1,2,3]`, [3]int64{1, 2, 3}, true}, - "array int64 large": {`["1","2","3"]`, [4]int64{1, 2, 3, 4}, true}, - "array int64 small": {`["1","2","3"]`, [2]int64{1, 2}, true}, - "map bytes": {`{"b":"AQID"}`, map[string][]byte{"b": {1, 2, 3}}, false}, - "map int32": {`{"a":1,"b":2}`, map[string]int32{"a": 1, "b": 2}, false}, - "map int64": {`{"a":"1","b":"2"}`, map[string]int64{"a": 1, "b": 2}, false}, - "map int64 empty": {`{}`, map[string]int64{}, false}, - "map int64 null": {`null`, map[string]int64(nil), false}, - "map int key": {`{}`, map[int]int{}, true}, - "time": {`"2020-06-03T17:35:30Z"`, time.Date(2020, 6, 3, 17, 35, 30, 0, time.UTC), false}, - "time non-utc": {`"2020-06-03T17:35:30+02:00"`, time.Time{}, true}, - "time nozone": {`"2020-06-03T17:35:30"`, time.Time{}, true}, - "car": {`{"type":"vehicle/car","value":{"Wheels":4}}`, Car{Wheels: 4}, false}, - "car ptr": {`{"type":"vehicle/car","value":{"Wheels":4}}`, &Car{Wheels: 4}, false}, - "car iface": {`{"type":"vehicle/car","value":{"Wheels":4}}`, Vehicle(&Car{Wheels: 4}), false}, - "boat": {`{"type":"vehicle/boat","value":{"Sail":true}}`, Boat{Sail: true}, false}, - "boat ptr": {`{"type":"vehicle/boat","value":{"Sail":true}}`, &Boat{Sail: true}, false}, - "boat iface": {`{"type":"vehicle/boat","value":{"Sail":true}}`, Vehicle(Boat{Sail: true}), false}, - "boat into car": {`{"type":"vehicle/boat","value":{"Sail":true}}`, Car{}, true}, - "boat into car iface": {`{"type":"vehicle/boat","value":{"Sail":true}}`, Vehicle(&Car{}), true}, - "shoes": {`{"type":"vehicle/shoes","value":{"Soles":"rubber"}}`, Car{}, true}, - "shoes ptr": {`{"type":"vehicle/shoes","value":{"Soles":"rubber"}}`, &Car{}, true}, - "shoes iface": {`{"type":"vehicle/shoes","value":{"Soles":"rubbes"}}`, Vehicle(&Car{}), true}, - "key public": {`{"type":"key/public","value":"AQIDBAUGBwg="}`, PublicKey{1, 2, 3, 4, 5, 6, 7, 8}, false}, - "key wrong": {`{"type":"key/public","value":"AQIDBAUGBwg="}`, PrivateKey{1, 2, 3, 4, 5, 6, 7, 8}, true}, - "key into car": {`{"type":"key/public","value":"AQIDBAUGBwg="}`, Vehicle(&Car{}), true}, - "tags": { - `{"name":"name","OmitEmpty":"foo","Hidden":"bar","tags":{"name":"child"}}`, - Tags{JSONName: "name", OmitEmpty: "foo", Tags: &Tags{JSONName: "child"}}, - false, - }, - "tags ptr": { - `{"name":"name","OmitEmpty":"foo","tags":null}`, - &Tags{JSONName: "name", OmitEmpty: "foo"}, - false, - }, - "tags real name": {`{"JSONName":"name"}`, Tags{}, false}, - "struct": { - `{ - "Bool":true, "Float64":3.14, "Int32":32, "Int64":"64", "Int64Ptr":"64", - "String":"foo", "StringPtrPtr": "string", "Bytes":"AQID", - "Time":"2020-06-02T16:05:13.004346374Z", - "Car":{"Wheels":4}, - "Boat":{"Sail":true}, - "Vehicles":[ - {"type":"vehicle/car","value":{"Wheels":4}}, - {"type":"vehicle/boat","value":{"Sail":true}} - ], - "Child":{ - "Bool":false, "Float64":0, "Int32":0, "Int64":"0", "Int64Ptr":null, - "String":"child", "StringPtrPtr":null, "Bytes":null, - "Time":"0001-01-01T00:00:00Z", - "Car":null, "Boat":{"Sail":false}, "Vehicles":null, "Child":null - }, - "private": "foo", "unknown": "bar" - }`, - Struct{ - Bool: true, Float64: 3.14, Int32: 32, Int64: 64, Int64Ptr: &i64, - String: "foo", StringPtrPtr: &strPtr, Bytes: []byte{1, 2, 3}, - Time: time.Date(2020, 6, 2, 16, 5, 13, 4346374, time.UTC), - Car: &Car{Wheels: 4}, Boat: Boat{Sail: true}, Vehicles: []Vehicle{ - Vehicle(&Car{Wheels: 4}), - Vehicle(Boat{Sail: true}), - }, - Child: &Struct{Bool: false, String: "child"}, - }, - false, - }, - "struct key into vehicle": {`{"Vehicles":[ - {"type":"vehicle/car","value":{"Wheels":4}}, - {"type":"key/public","value":"MTIzNDU2Nzg="} - ]}`, Struct{}, true}, - "struct ptr null": {`null`, structNil, false}, - "custom value": {`{"Value":"foo"}`, CustomValue{}, false}, - "custom ptr": {`"foo"`, &CustomPtr{Value: "custom"}, false}, - "custom ptr value": {`"foo"`, CustomPtr{Value: "custom"}, false}, - "invalid type": {`"foo"`, Struct{}, true}, - } - for name, tc := range testcases { - tc := tc - t.Run(name, func(t *testing.T) { - // Create a target variable as a pointer to the zero value of the tc.value type, - // and wrap it in an empty interface. Decode into that interface. - target := reflect.New(reflect.TypeOf(tc.value)).Interface() - err := json.Unmarshal([]byte(tc.json), target) - if tc.err { - require.Error(t, err) - return - } - require.NoError(t, err) - - // Unwrap the target pointer and get the value behind the interface. - actual := reflect.ValueOf(target).Elem().Interface() - assert.Equal(t, tc.value, actual) - }) - } -} diff --git a/libs/json/doc.go b/libs/json/doc.go deleted file mode 100644 index d5ef4047f..000000000 --- a/libs/json/doc.go +++ /dev/null @@ -1,99 +0,0 @@ -// Package json provides functions for marshaling and unmarshaling JSON in a format that is -// backwards-compatible with Amino JSON encoding. This mostly differs from encoding/json in -// encoding of integers (64-bit integers are encoded as strings, not numbers), and handling -// of interfaces (wrapped in an interface object with type/value keys). -// -// JSON tags (e.g. `json:"name,omitempty"`) are supported in the same way as encoding/json, as is -// custom marshaling overrides via the json.Marshaler and json.Unmarshaler interfaces. -// -// Note that not all JSON emitted by Tendermint is generated by this library; some is generated by -// encoding/json instead, and kept like that for backwards compatibility. -// -// Encoding of numbers uses strings for 64-bit integers (including unspecified ints), to improve -// compatibility with e.g. Javascript (which uses 64-bit floats for numbers, having 53-bit -// precision): -// -// int32(32) // Output: 32 -// uint32(32) // Output: 32 -// int64(64) // Output: "64" -// uint64(64) // Output: "64" -// int(64) // Output: "64" -// uint(64) // Output: "64" -// -// Encoding of other scalars follows encoding/json: -// -// nil // Output: null -// true // Output: true -// "foo" // Output: "foo" -// "" // Output: "" -// -// Slices and arrays are encoded as encoding/json, including base64-encoding of byte slices -// with additional base64-encoding of byte arrays as well: -// -// []int64(nil) // Output: null -// []int64{} // Output: [] -// []int64{1, 2, 3} // Output: ["1", "2", "3"] -// []int32{1, 2, 3} // Output: [1, 2, 3] -// []byte{1, 2, 3} // Output: "AQID" -// [3]int64{1, 2, 3} // Output: ["1", "2", "3"] -// [3]byte{1, 2, 3} // Output: "AQID" -// -// Maps are encoded as encoding/json, but only strings are allowed as map keys (nil maps are not -// emitted as null, to retain Amino backwards-compatibility): -// -// map[string]int64(nil) // Output: {} -// map[string]int64{} // Output: {} -// map[string]int64{"a":1,"b":2} // Output: {"a":"1","b":"2"} -// map[string]int32{"a":1,"b":2} // Output: {"a":1,"b":2} -// map[bool]int{true:1} // Errors -// -// Times are encoded as encoding/json, in RFC3339Nano format, but requiring UTC time zone (with zero -// times emitted as "0001-01-01T00:00:00Z" as with encoding/json): -// -// time.Date(2020, 6, 8, 16, 21, 28, 123, time.FixedZone("UTC+2", 2*60*60)) -// // Output: "2020-06-08T14:21:28.000000123Z" -// time.Time{} // Output: "0001-01-01T00:00:00Z" -// (*time.Time)(nil) // Output: null -// -// Structs are encoded as encoding/json, supporting JSON tags and ignoring private fields: -// -// type Struct struct{ -// Name string -// Value int32 `json:"value,omitempty"` -// private bool -// } -// -// Struct{Name: "foo", Value: 7, private: true} // Output: {"Name":"foo","value":7} -// Struct{} // Output: {"Name":""} -// -// Registered types are encoded with type wrapper, regardless of whether they are given as interface -// or bare struct, but inside structs they are only emitted with type wrapper for interface fields -// (this follows Amino behavior): -// -// type Vehicle interface { -// Drive() error -// } -// -// type Car struct { -// Wheels int8 -// } -// -// func (c *Car) Drive() error { return nil } -// -// RegisterType(&Car{}, "vehicle/car") -// -// Car{Wheels: 4} // Output: {"type":"vehicle/car","value":{"Wheels":4}} -// &Car{Wheels: 4} // Output: {"type":"vehicle/car","value":{"Wheels":4}} -// (*Car)(nil) // Output: null -// Vehicle(Car{Wheels: 4}) // Output: {"type":"vehicle/car","value":{"Wheels":4}} -// Vehicle(nil) // Output: null -// -// type Struct struct { -// Car *Car -// Vehicle Vehicle -// } -// -// Struct{Car: &Car{Wheels: 4}, Vehicle: &Car{Wheels: 4}} -// // Output: {"Car": {"Wheels: 4"}, "Vehicle": {"type":"vehicle/car","value":{"Wheels":4}}} -// -package json diff --git a/libs/json/encoder.go b/libs/json/encoder.go deleted file mode 100644 index 11990e2af..000000000 --- a/libs/json/encoder.go +++ /dev/null @@ -1,254 +0,0 @@ -package json - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "reflect" - "strconv" - "time" -) - -var ( - timeType = reflect.TypeOf(time.Time{}) - jsonMarshalerType = reflect.TypeOf(new(json.Marshaler)).Elem() - jsonUnmarshalerType = reflect.TypeOf(new(json.Unmarshaler)).Elem() -) - -// Marshal marshals the value as JSON, using Amino-compatible JSON encoding (strings for -// 64-bit numbers, and type wrappers for registered types). -func Marshal(v interface{}) ([]byte, error) { - buf := new(bytes.Buffer) - err := encode(buf, v) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// MarshalIndent marshals the value as JSON, using the given prefix and indentation. -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { - bz, err := Marshal(v) - if err != nil { - return nil, err - } - buf := new(bytes.Buffer) - err = json.Indent(buf, bz, prefix, indent) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func encode(w io.Writer, v interface{}) error { - // Bare nil values can't be reflected, so we must handle them here. - if v == nil { - return writeStr(w, "null") - } - rv := reflect.ValueOf(v) - - // If this is a registered type, defer to interface encoder regardless of whether the input is - // an interface or a bare value. This retains Amino's behavior, but is inconsistent with - // behavior in structs where an interface field will get the type wrapper while a bare value - // field will not. - if typeRegistry.name(rv.Type()) != "" { - return encodeReflectInterface(w, rv) - } - - return encodeReflect(w, rv) -} - -func encodeReflect(w io.Writer, rv reflect.Value) error { - if !rv.IsValid() { - return errors.New("invalid reflect value") - } - - // Recursively dereference if pointer. - for rv.Kind() == reflect.Ptr { - if rv.IsNil() { - return writeStr(w, "null") - } - rv = rv.Elem() - } - - // Convert times to UTC. - if rv.Type() == timeType { - rv = reflect.ValueOf(rv.Interface().(time.Time).Round(0).UTC()) - } - - // If the value implements json.Marshaler, defer to stdlib directly. Since we've already - // dereferenced, we try implementations with both value receiver and pointer receiver. We must - // do this after the time normalization above, and thus after dereferencing. - if rv.Type().Implements(jsonMarshalerType) { - return encodeStdlib(w, rv.Interface()) - } else if rv.CanAddr() && rv.Addr().Type().Implements(jsonMarshalerType) { - return encodeStdlib(w, rv.Addr().Interface()) - } - - switch rv.Type().Kind() { - // Complex types must be recursively encoded. - case reflect.Interface: - return encodeReflectInterface(w, rv) - - case reflect.Array, reflect.Slice: - return encodeReflectList(w, rv) - - case reflect.Map: - return encodeReflectMap(w, rv) - - case reflect.Struct: - return encodeReflectStruct(w, rv) - - // 64-bit integers are emitted as strings, to avoid precision problems with e.g. - // Javascript which uses 64-bit floats (having 53-bit precision). - case reflect.Int64, reflect.Int: - return writeStr(w, `"`+strconv.FormatInt(rv.Int(), 10)+`"`) - - case reflect.Uint64, reflect.Uint: - return writeStr(w, `"`+strconv.FormatUint(rv.Uint(), 10)+`"`) - - // For everything else, defer to the stdlib encoding/json encoder - default: - return encodeStdlib(w, rv.Interface()) - } -} - -func encodeReflectList(w io.Writer, rv reflect.Value) error { - // Emit nil slices as null. - if rv.Kind() == reflect.Slice && rv.IsNil() { - return writeStr(w, "null") - } - - // Encode byte slices as base64 with the stdlib encoder. - if rv.Type().Elem().Kind() == reflect.Uint8 { - // Stdlib does not base64-encode byte arrays, only slices, so we copy to slice. - if rv.Type().Kind() == reflect.Array { - slice := reflect.MakeSlice(reflect.SliceOf(rv.Type().Elem()), rv.Len(), rv.Len()) - reflect.Copy(slice, rv) - rv = slice - } - return encodeStdlib(w, rv.Interface()) - } - - // Anything else we recursively encode ourselves. - length := rv.Len() - if err := writeStr(w, "["); err != nil { - return err - } - for i := 0; i < length; i++ { - if err := encodeReflect(w, rv.Index(i)); err != nil { - return err - } - if i < length-1 { - if err := writeStr(w, ","); err != nil { - return err - } - } - } - return writeStr(w, "]") -} - -func encodeReflectMap(w io.Writer, rv reflect.Value) error { - if rv.Type().Key().Kind() != reflect.String { - return errors.New("map key must be string") - } - - // nil maps are not emitted as nil, to retain Amino compatibility. - - if err := writeStr(w, "{"); err != nil { - return err - } - writeComma := false - for _, keyrv := range rv.MapKeys() { - if writeComma { - if err := writeStr(w, ","); err != nil { - return err - } - } - if err := encodeStdlib(w, keyrv.Interface()); err != nil { - return err - } - if err := writeStr(w, ":"); err != nil { - return err - } - if err := encodeReflect(w, rv.MapIndex(keyrv)); err != nil { - return err - } - writeComma = true - } - return writeStr(w, "}") -} - -func encodeReflectStruct(w io.Writer, rv reflect.Value) error { - sInfo := makeStructInfo(rv.Type()) - if err := writeStr(w, "{"); err != nil { - return err - } - writeComma := false - for i, fInfo := range sInfo.fields { - frv := rv.Field(i) - if fInfo.hidden || (fInfo.omitEmpty && frv.IsZero()) { - continue - } - - if writeComma { - if err := writeStr(w, ","); err != nil { - return err - } - } - if err := encodeStdlib(w, fInfo.jsonName); err != nil { - return err - } - if err := writeStr(w, ":"); err != nil { - return err - } - if err := encodeReflect(w, frv); err != nil { - return err - } - writeComma = true - } - return writeStr(w, "}") -} - -func encodeReflectInterface(w io.Writer, rv reflect.Value) error { - // Get concrete value and dereference pointers. - for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface { - if rv.IsNil() { - return writeStr(w, "null") - } - rv = rv.Elem() - } - - // Look up the name of the concrete type - name := typeRegistry.name(rv.Type()) - if name == "" { - return fmt.Errorf("cannot encode unregistered type %v", rv.Type()) - } - - // Write value wrapped in interface envelope - if err := writeStr(w, fmt.Sprintf(`{"type":%q,"value":`, name)); err != nil { - return err - } - if err := encodeReflect(w, rv); err != nil { - return err - } - return writeStr(w, "}") -} - -func encodeStdlib(w io.Writer, v interface{}) error { - // Doesn't stream the output because that adds a newline, as per: - // https://golang.org/pkg/encoding/json/#Encoder.Encode - blob, err := json.Marshal(v) - if err != nil { - return err - } - _, err = w.Write(blob) - return err -} - -func writeStr(w io.Writer, s string) error { - _, err := w.Write([]byte(s)) - return err -} diff --git a/libs/json/encoder_test.go b/libs/json/encoder_test.go deleted file mode 100644 index 88eb56f85..000000000 --- a/libs/json/encoder_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package json_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/tendermint/tendermint/libs/json" -) - -func TestMarshal(t *testing.T) { - s := "string" - sPtr := &s - i64 := int64(64) - ti := time.Date(2020, 6, 2, 18, 5, 13, 4346374, time.FixedZone("UTC+2", 2*60*60)) - car := &Car{Wheels: 4} - boat := Boat{Sail: true} - - testcases := map[string]struct { - value interface{} - output string - }{ - "nil": {nil, `null`}, - "string": {"foo", `"foo"`}, - "float32": {float32(3.14), `3.14`}, - "float32 neg": {float32(-3.14), `-3.14`}, - "float64": {float64(3.14), `3.14`}, - "float64 neg": {float64(-3.14), `-3.14`}, - "int32": {int32(32), `32`}, - "int64": {int64(64), `"64"`}, - "int64 neg": {int64(-64), `"-64"`}, - "int64 ptr": {&i64, `"64"`}, - "uint64": {uint64(64), `"64"`}, - "time": {ti, `"2020-06-02T16:05:13.004346374Z"`}, - "time empty": {time.Time{}, `"0001-01-01T00:00:00Z"`}, - "time ptr": {&ti, `"2020-06-02T16:05:13.004346374Z"`}, - "customptr": {CustomPtr{Value: "x"}, `{"Value":"x"}`}, // same as encoding/json - "customptr ptr": {&CustomPtr{Value: "x"}, `"custom"`}, - "customvalue": {CustomValue{Value: "x"}, `"custom"`}, - "customvalue ptr": {&CustomValue{Value: "x"}, `"custom"`}, - "slice nil": {[]int(nil), `null`}, - "slice empty": {[]int{}, `[]`}, - "slice bytes": {[]byte{1, 2, 3}, `"AQID"`}, - "slice int64": {[]int64{1, 2, 3}, `["1","2","3"]`}, - "slice int64 ptr": {[]*int64{&i64, nil}, `["64",null]`}, - "array bytes": {[3]byte{1, 2, 3}, `"AQID"`}, - "array int64": {[3]int64{1, 2, 3}, `["1","2","3"]`}, - "map nil": {map[string]int64(nil), `{}`}, // retain Amino compatibility - "map empty": {map[string]int64{}, `{}`}, - "map int64": {map[string]int64{"a": 1, "b": 2, "c": 3}, `{"a":"1","b":"2","c":"3"}`}, - "car": {car, `{"type":"vehicle/car","value":{"Wheels":4}}`}, - "car value": {*car, `{"type":"vehicle/car","value":{"Wheels":4}}`}, - "car iface": {Vehicle(car), `{"type":"vehicle/car","value":{"Wheels":4}}`}, - "car nil": {(*Car)(nil), `null`}, - "boat": {boat, `{"type":"vehicle/boat","value":{"Sail":true}}`}, - "boat ptr": {&boat, `{"type":"vehicle/boat","value":{"Sail":true}}`}, - "boat iface": {Vehicle(boat), `{"type":"vehicle/boat","value":{"Sail":true}}`}, - "key public": {PublicKey{1, 2, 3, 4, 5, 6, 7, 8}, `{"type":"key/public","value":"AQIDBAUGBwg="}`}, - "tags": { - Tags{JSONName: "name", OmitEmpty: "foo", Hidden: "bar", Tags: &Tags{JSONName: "child"}}, - `{"name":"name","OmitEmpty":"foo","tags":{"name":"child"}}`, - }, - "tags empty": {Tags{}, `{"name":""}`}, - // The encoding of the Car and Boat fields do not have type wrappers, even though they get - // type wrappers when encoded directly (see "car" and "boat" tests). This is to retain the - // same behavior as Amino. If the field was a Vehicle interface instead, it would get - // type wrappers, as seen in the Vehicles field. - "struct": { - Struct{ - Bool: true, Float64: 3.14, Int32: 32, Int64: 64, Int64Ptr: &i64, - String: "foo", StringPtrPtr: &sPtr, Bytes: []byte{1, 2, 3}, - Time: ti, Car: car, Boat: boat, Vehicles: []Vehicle{car, boat}, - Child: &Struct{Bool: false, String: "child"}, private: "private", - }, - `{ - "Bool":true, "Float64":3.14, "Int32":32, "Int64":"64", "Int64Ptr":"64", - "String":"foo", "StringPtrPtr": "string", "Bytes":"AQID", - "Time":"2020-06-02T16:05:13.004346374Z", - "Car":{"Wheels":4}, - "Boat":{"Sail":true}, - "Vehicles":[ - {"type":"vehicle/car","value":{"Wheels":4}}, - {"type":"vehicle/boat","value":{"Sail":true}} - ], - "Child":{ - "Bool":false, "Float64":0, "Int32":0, "Int64":"0", "Int64Ptr":null, - "String":"child", "StringPtrPtr":null, "Bytes":null, - "Time":"0001-01-01T00:00:00Z", - "Car":null, "Boat":{"Sail":false}, "Vehicles":null, "Child":null - } - }`, - }, - } - for name, tc := range testcases { - tc := tc - t.Run(name, func(t *testing.T) { - bz, err := json.Marshal(tc.value) - require.NoError(t, err) - assert.JSONEq(t, tc.output, string(bz)) - }) - } -} diff --git a/libs/json/helpers_test.go b/libs/json/helpers_test.go deleted file mode 100644 index ccb3c0038..000000000 --- a/libs/json/helpers_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package json_test - -import ( - "time" - - "github.com/tendermint/tendermint/libs/json" -) - -// Register Car, an instance of the Vehicle interface. -func init() { - json.RegisterType(&Car{}, "vehicle/car") - json.RegisterType(Boat{}, "vehicle/boat") - json.RegisterType(PublicKey{}, "key/public") - json.RegisterType(PrivateKey{}, "key/private") -} - -type Vehicle interface { - Drive() error -} - -// Car is a pointer implementation of Vehicle. -type Car struct { - Wheels int32 -} - -func (c *Car) Drive() error { return nil } - -// Boat is a value implementation of Vehicle. -type Boat struct { - Sail bool -} - -func (b Boat) Drive() error { return nil } - -// These are public and private encryption keys. -type PublicKey [8]byte -type PrivateKey [8]byte - -// Custom has custom marshalers and unmarshalers, taking pointer receivers. -type CustomPtr struct { - Value string -} - -func (c *CustomPtr) MarshalJSON() ([]byte, error) { - return []byte("\"custom\""), nil -} - -func (c *CustomPtr) UnmarshalJSON(bz []byte) error { - c.Value = "custom" - return nil -} - -// CustomValue has custom marshalers and unmarshalers, taking value receivers (which usually doesn't -// make much sense since the unmarshaler can't change anything). -type CustomValue struct { - Value string -} - -func (c CustomValue) MarshalJSON() ([]byte, error) { - return []byte("\"custom\""), nil -} - -func (c CustomValue) UnmarshalJSON(bz []byte) error { - return nil -} - -// Tags tests JSON tags. -type Tags struct { - JSONName string `json:"name"` - OmitEmpty string `json:",omitempty"` - Hidden string `json:"-"` - Tags *Tags `json:"tags,omitempty"` -} - -// Struct tests structs with lots of contents. -type Struct struct { - Bool bool - Float64 float64 - Int32 int32 - Int64 int64 - Int64Ptr *int64 - String string - StringPtrPtr **string - Bytes []byte - Time time.Time - Car *Car - Boat Boat - Vehicles []Vehicle - Child *Struct - private string -} diff --git a/libs/json/structs.go b/libs/json/structs.go deleted file mode 100644 index b20873c33..000000000 --- a/libs/json/structs.go +++ /dev/null @@ -1,87 +0,0 @@ -package json - -import ( - "fmt" - "reflect" - "strings" - "sync" - "unicode" -) - -var ( - // cache caches struct info. - cache = newStructInfoCache() -) - -// structCache is a cache of struct info. -type structInfoCache struct { - sync.RWMutex - structInfos map[reflect.Type]*structInfo -} - -func newStructInfoCache() *structInfoCache { - return &structInfoCache{ - structInfos: make(map[reflect.Type]*structInfo), - } -} - -func (c *structInfoCache) get(rt reflect.Type) *structInfo { - c.RLock() - defer c.RUnlock() - return c.structInfos[rt] -} - -func (c *structInfoCache) set(rt reflect.Type, sInfo *structInfo) { - c.Lock() - defer c.Unlock() - c.structInfos[rt] = sInfo -} - -// structInfo contains JSON info for a struct. -type structInfo struct { - fields []*fieldInfo -} - -// fieldInfo contains JSON info for a struct field. -type fieldInfo struct { - jsonName string - omitEmpty bool - hidden bool -} - -// makeStructInfo generates structInfo for a struct as a reflect.Value. -func makeStructInfo(rt reflect.Type) *structInfo { - if rt.Kind() != reflect.Struct { - panic(fmt.Sprintf("can't make struct info for non-struct value %v", rt)) - } - if sInfo := cache.get(rt); sInfo != nil { - return sInfo - } - fields := make([]*fieldInfo, 0, rt.NumField()) - for i := 0; i < cap(fields); i++ { - frt := rt.Field(i) - fInfo := &fieldInfo{ - jsonName: frt.Name, - omitEmpty: false, - hidden: frt.Name == "" || !unicode.IsUpper(rune(frt.Name[0])), - } - o := frt.Tag.Get("json") - if o == "-" { - fInfo.hidden = true - } else if o != "" { - opts := strings.Split(o, ",") - if opts[0] != "" { - fInfo.jsonName = opts[0] - } - for _, o := range opts[1:] { - if o == "omitempty" { - fInfo.omitEmpty = true - } - } - } - fields = append(fields, fInfo) - } - sInfo := &structInfo{fields: fields} - cache.set(rt, sInfo) - return sInfo -} diff --git a/libs/json/types.go b/libs/json/types.go deleted file mode 100644 index 9c9493056..000000000 --- a/libs/json/types.go +++ /dev/null @@ -1,108 +0,0 @@ -package json - -import ( - "errors" - "fmt" - "reflect" - "sync" -) - -var ( - // typeRegistry contains globally registered types for JSON encoding/decoding. - typeRegistry = newTypes() -) - -// RegisterType registers a type for Amino-compatible interface encoding in the global type -// registry. These types will be encoded with a type wrapper `{"type":"","value":}` -// regardless of which interface they are wrapped in (if any). If the type is a pointer, it will -// still be valid both for value and pointer types, but decoding into an interface will generate -// the a value or pointer based on the registered type. -// -// Should only be called in init() functions, as it panics on error. -func RegisterType(_type interface{}, name string) { - if _type == nil { - panic("cannot register nil type") - } - err := typeRegistry.register(name, reflect.ValueOf(_type).Type()) - if err != nil { - panic(err) - } -} - -// typeInfo contains type information. -type typeInfo struct { - name string - rt reflect.Type - returnPtr bool -} - -// types is a type registry. It is safe for concurrent use. -type types struct { - sync.RWMutex - byType map[reflect.Type]*typeInfo - byName map[string]*typeInfo -} - -// newTypes creates a new type registry. -func newTypes() types { - return types{ - byType: map[reflect.Type]*typeInfo{}, - byName: map[string]*typeInfo{}, - } -} - -// registers the given type with the given name. The name and type must not be registered already. -func (t *types) register(name string, rt reflect.Type) error { - if name == "" { - return errors.New("name cannot be empty") - } - // If this is a pointer type, we recursively resolve until we get a bare type, but register that - // we should return pointers. - returnPtr := false - for rt.Kind() == reflect.Ptr { - returnPtr = true - rt = rt.Elem() - } - tInfo := &typeInfo{ - name: name, - rt: rt, - returnPtr: returnPtr, - } - - t.Lock() - defer t.Unlock() - if _, ok := t.byName[tInfo.name]; ok { - return fmt.Errorf("a type with name %q is already registered", name) - } - if _, ok := t.byType[tInfo.rt]; ok { - return fmt.Errorf("the type %v is already registered", rt) - } - t.byName[name] = tInfo - t.byType[rt] = tInfo - return nil -} - -// lookup looks up a type from a name, or nil if not registered. -func (t *types) lookup(name string) (reflect.Type, bool) { - t.RLock() - defer t.RUnlock() - tInfo := t.byName[name] - if tInfo == nil { - return nil, false - } - return tInfo.rt, tInfo.returnPtr -} - -// name looks up the name of a type, or empty if not registered. Unwraps pointers as necessary. -func (t *types) name(rt reflect.Type) string { - for rt.Kind() == reflect.Ptr { - rt = rt.Elem() - } - t.RLock() - defer t.RUnlock() - tInfo := t.byType[rt] - if tInfo == nil { - return "" - } - return tInfo.name -} diff --git a/light/mbt/driver_test.go b/light/mbt/driver_test.go index f61c3b234..55a05a607 100644 --- a/light/mbt/driver_test.go +++ b/light/mbt/driver_test.go @@ -1,6 +1,7 @@ package mbt import ( + "encoding/json" "os" "path/filepath" "testing" @@ -8,7 +9,6 @@ import ( "github.com/stretchr/testify/require" - tmjson "github.com/tendermint/tendermint/libs/json" "github.com/tendermint/tendermint/light" "github.com/tendermint/tendermint/types" ) @@ -28,7 +28,7 @@ func TestVerify(t *testing.T) { } var tc testCase - err = tmjson.Unmarshal(jsonBlob, &tc) + err = json.Unmarshal(jsonBlob, &tc) if err != nil { t.Fatal(err) } @@ -103,7 +103,7 @@ type testCase struct { type initialData struct { SignedHeader types.SignedHeader `json:"signed_header"` NextValidatorSet types.ValidatorSet `json:"next_validator_set"` - TrustingPeriod uint64 `json:"trusting_period"` + TrustingPeriod uint64 `json:"trusting_period,string"` Now time.Time `json:"now"` } diff --git a/proto/tendermint/crypto/crypto.go b/proto/tendermint/crypto/crypto.go new file mode 100644 index 000000000..66b34cc2b --- /dev/null +++ b/proto/tendermint/crypto/crypto.go @@ -0,0 +1,7 @@ +package crypto + +// These functions export type tags for use with internal/jsontypes. + +func (*PublicKey) TypeTag() string { return "tendermint.crypto.PublicKey" } +func (*PublicKey_Ed25519) TypeTag() string { return "tendermint.crypto.PublicKey_Ed25519" } +func (*PublicKey_Secp256K1) TypeTag() string { return "tendermint.crypto.PublicKey_Secp256K1" } diff --git a/scripts/json2wal/main.go b/scripts/json2wal/main.go index 251230bd1..e8d3fcf93 100644 --- a/scripts/json2wal/main.go +++ b/scripts/json2wal/main.go @@ -9,13 +9,13 @@ package main import ( "bufio" + "encoding/json" "fmt" "io" "os" "strings" "github.com/tendermint/tendermint/internal/consensus" - tmjson "github.com/tendermint/tendermint/libs/json" "github.com/tendermint/tendermint/types" ) @@ -56,7 +56,7 @@ func main() { } var msg consensus.TimedWALMessage - err = tmjson.Unmarshal(msgJSON, &msg) + err = json.Unmarshal(msgJSON, &msg) if err != nil { panic(fmt.Errorf("failed to unmarshal json: %w", err)) } diff --git a/scripts/wal2json/main.go b/scripts/wal2json/main.go index 98074f442..7ee756106 100644 --- a/scripts/wal2json/main.go +++ b/scripts/wal2json/main.go @@ -8,12 +8,12 @@ package main import ( + "encoding/json" "fmt" "io" "os" "github.com/tendermint/tendermint/internal/consensus" - tmjson "github.com/tendermint/tendermint/libs/json" ) func main() { @@ -37,7 +37,7 @@ func main() { panic(fmt.Errorf("failed to decode msg: %w", err)) } - json, err := tmjson.Marshal(msg) + json, err := json.Marshal(msg) if err != nil { panic(fmt.Errorf("failed to marshal msg: %w", err)) } diff --git a/types/proposal.go b/types/proposal.go index 5f3f50bf7..3f06738fb 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -24,7 +24,7 @@ var ( // If POLRound >= 0, then BlockID corresponds to the block that is locked in POLRound. type Proposal struct { Type tmproto.SignedMsgType - Height int64 `json:"height"` + Height int64 `json:"height,string"` Round int32 `json:"round"` // there can not be greater than 2_147_483_647 rounds POLRound int32 `json:"pol_round"` // -1 if null. BlockID BlockID `json:"block_id"`