Browse Source

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.
pull/7685/head
M. J. Fromberger 2 years ago
committed by GitHub
parent
commit
7878ca6a8a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 104 additions and 1216 deletions
  1. +1
    -0
      CHANGELOG_PENDING.md
  2. +4
    -4
      crypto/encoding/codec.go
  3. +4
    -4
      crypto/merkle/proof.go
  4. +38
    -18
      internal/consensus/msgs.go
  5. +34
    -4
      internal/consensus/state.go
  6. +1
    -1
      internal/consensus/types/round_state.go
  7. +7
    -5
      internal/consensus/wal.go
  8. +0
    -278
      libs/json/decoder.go
  9. +0
    -151
      libs/json/decoder_test.go
  10. +0
    -99
      libs/json/doc.go
  11. +0
    -254
      libs/json/encoder.go
  12. +0
    -104
      libs/json/encoder_test.go
  13. +0
    -91
      libs/json/helpers_test.go
  14. +0
    -87
      libs/json/structs.go
  15. +0
    -108
      libs/json/types.go
  16. +3
    -3
      light/mbt/driver_test.go
  17. +7
    -0
      proto/tendermint/crypto/crypto.go
  18. +2
    -2
      scripts/json2wal/main.go
  19. +2
    -2
      scripts/wal2json/main.go
  20. +1
    -1
      types/proposal.go

+ 1
- 0
CHANGELOG_PENDING.md View File

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


+ 4
- 4
crypto/encoding/codec.go View File

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


+ 4
- 4
crypto/merkle/proof.go View File

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


+ 38
- 18
internal/consensus/msgs.go View File

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


+ 34
- 4
internal/consensus/state.go View File

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


+ 1
- 1
internal/consensus/types/round_state.go View File

@ -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"`


+ 7
- 5
internal/consensus/wal.go View File

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


+ 0
- 278
libs/json/decoder.go View File

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

+ 0
- 151
libs/json/decoder_test.go View File

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

+ 0
- 99
libs/json/doc.go View File

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

+ 0
- 254
libs/json/encoder.go View File

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

+ 0
- 104
libs/json/encoder_test.go View File

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

+ 0
- 91
libs/json/helpers_test.go View File

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

+ 0
- 87
libs/json/structs.go View File

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

+ 0
- 108
libs/json/types.go View File

@ -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":"<type>","value":<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
}

+ 3
- 3
light/mbt/driver_test.go View File

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


+ 7
- 0
proto/tendermint/crypto/crypto.go View File

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

+ 2
- 2
scripts/json2wal/main.go View File

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


+ 2
- 2
scripts/wal2json/main.go View File

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


+ 1
- 1
types/proposal.go View File

@ -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"`


Loading…
Cancel
Save