Skip to content

Commit b9dd464

Browse files
author
Jason Yellick
committed
[FAB-4105] Fab proto translation methods
With the work done for FAB-4100 through FAB-4104, it is now possible to add a few small descriptive methods to proto methods, and the proto translator framework can then transalte the binary proto (including fields which would ordinarilly be expressed as binary, or nested binary) into pure binary-less JSON. This CR adds the methods necessary to annotate the fabric protos related to configuration. This now allows the proto translation framework to print fully human readable versions of both config update messages, as well as of configuration blocks (and any subset thereof). This CR appears to introduce substantial amounts of code with very little tests. However, the two tests in blackbox_test use every method introduced. Additionally, because these methods are only used for proto printing, they can have no affect on the stability of the fabric overall. This is a key component for FAB-1678. Change-Id: If166a55097dccef5208c1dc2ccd3bf039b7637d0 Signed-off-by: Jason Yellick <[email protected]>
1 parent 7c4fcbf commit b9dd464

File tree

10 files changed

+846
-3
lines changed

10 files changed

+846
-3
lines changed
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protolator_test
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
genesisconfig "github.com/hyperledger/fabric/common/configtx/tool/localconfig"
24+
"github.com/hyperledger/fabric/common/configtx/tool/provisional"
25+
. "github.com/hyperledger/fabric/common/tools/protolator"
26+
27+
"github.com/golang/protobuf/proto"
28+
"github.com/stretchr/testify/assert"
29+
)
30+
31+
func bidirectionalMarshal(t *testing.T, doc proto.Message) {
32+
var buffer bytes.Buffer
33+
34+
assert.NoError(t, DeepMarshalJSON(&buffer, doc))
35+
36+
newRoot := proto.Clone(doc)
37+
newRoot.Reset()
38+
assert.NoError(t, DeepUnmarshalJSON(bytes.NewReader(buffer.Bytes()), newRoot))
39+
40+
// Note, we cannot do an equality check between newRoot and sampleDoc
41+
// because of the nondeterministic nature of binary proto marshaling
42+
// So instead we re-marshal to JSON which is a deterministic marshaling
43+
// and compare equality there instead
44+
45+
//t.Log(doc)
46+
//t.Log(newRoot)
47+
48+
var remarshaled bytes.Buffer
49+
assert.NoError(t, DeepMarshalJSON(&remarshaled, newRoot))
50+
assert.Equal(t, string(buffer.Bytes()), string(remarshaled.Bytes()))
51+
//t.Log(string(buffer.Bytes()))
52+
//t.Log(string(remarshaled.Bytes()))
53+
}
54+
55+
func TestConfigUpdate(t *testing.T) {
56+
p := provisional.New(genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile))
57+
ct := p.ChannelTemplate()
58+
cue, err := ct.Envelope("Foo")
59+
assert.NoError(t, err)
60+
61+
bidirectionalMarshal(t, cue)
62+
}
63+
64+
func TestGenesisBlock(t *testing.T) {
65+
p := provisional.New(genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile))
66+
gb := p.GenesisBlockForChannel("foo")
67+
68+
bidirectionalMarshal(t, gb)
69+
70+
}

common/tools/protolator/nested.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"reflect"
2121

2222
"github.com/golang/protobuf/proto"
23+
"github.com/golang/protobuf/ptypes/timestamp"
2324
)
2425

2526
func nestedFrom(value interface{}, destType reflect.Type) (reflect.Value, error) {
@@ -37,10 +38,14 @@ func nestedTo(value reflect.Value) (interface{}, error) {
3738
return recursivelyCreateTreeFromMessage(nMsg)
3839
}
3940

41+
var timestampType = reflect.TypeOf(&timestamp.Timestamp{})
42+
4043
type nestedFieldFactory struct{}
4144

4245
func (nff nestedFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
43-
return fieldType.Kind() == reflect.Ptr && fieldType.AssignableTo(protoMsgType)
46+
// Note, we skip recursing into the field if it is a proto native timestamp, because there is other custom marshaling this conflicts with
47+
// this should probably be revisited more generally to prevent custom marshaling of 'well known messages'
48+
return fieldType.Kind() == reflect.Ptr && fieldType.AssignableTo(protoMsgType) && !fieldType.AssignableTo(timestampType)
4449
}
4550

4651
func (nff nestedFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
@@ -60,7 +65,7 @@ func (nff nestedFieldFactory) NewProtoField(msg proto.Message, fieldName string,
6065
type nestedMapFieldFactory struct{}
6166

6267
func (nmff nestedMapFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
63-
return fieldType.Kind() == reflect.Map && fieldType.Elem().AssignableTo(protoMsgType)
68+
return fieldType.Kind() == reflect.Map && fieldType.Elem().AssignableTo(protoMsgType) && !fieldType.Elem().AssignableTo(timestampType)
6469
}
6570

6671
func (nmff nestedMapFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {
@@ -84,7 +89,7 @@ func (nmff nestedMapFieldFactory) NewProtoField(msg proto.Message, fieldName str
8489
type nestedSliceFieldFactory struct{}
8590

8691
func (nmff nestedSliceFieldFactory) Handles(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) bool {
87-
return fieldType.Kind() == reflect.Slice && fieldType.Elem().AssignableTo(protoMsgType)
92+
return fieldType.Kind() == reflect.Slice && fieldType.Elem().AssignableTo(protoMsgType) && !fieldType.Elem().AssignableTo(timestampType)
8893
}
8994

9095
func (nmff nestedSliceFieldFactory) NewProtoField(msg proto.Message, fieldName string, fieldType reflect.Type, fieldValue reflect.Value) (protoField, error) {

protos/common/common.go

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package common
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/golang/protobuf/proto"
23+
)
24+
25+
func (e *Envelope) StaticallyOpaqueFields() []string {
26+
return []string{"payload"}
27+
}
28+
29+
func (e *Envelope) StaticallyOpaqueFieldProto(name string) (proto.Message, error) {
30+
if name != e.StaticallyOpaqueFields()[0] {
31+
return nil, fmt.Errorf("not a marshaled field: %s", name)
32+
}
33+
return &Payload{}, nil
34+
}
35+
36+
func (p *Payload) VariablyOpaqueFields() []string {
37+
return []string{"data"}
38+
}
39+
40+
func (p *Payload) VariablyOpaqueFieldProto(name string) (proto.Message, error) {
41+
if name != p.VariablyOpaqueFields()[0] {
42+
return nil, fmt.Errorf("not a marshaled field: %s", name)
43+
}
44+
if p.Header == nil {
45+
return nil, fmt.Errorf("cannot determine payload type when header is missing")
46+
}
47+
ch := &ChannelHeader{}
48+
if err := proto.Unmarshal(p.Header.ChannelHeader, ch); err != nil {
49+
return nil, fmt.Errorf("corrupt channel header: %s", err)
50+
}
51+
52+
switch ch.Type {
53+
case int32(HeaderType_CONFIG):
54+
return &ConfigEnvelope{}, nil
55+
case int32(HeaderType_CONFIG_UPDATE):
56+
return &ConfigUpdateEnvelope{}, nil
57+
// TODO implement the other well known types, particularly endorser txs
58+
default:
59+
return nil, fmt.Errorf("decoding type %v is unimplemented", ch.Type)
60+
}
61+
}
62+
63+
func (h *Header) StaticallyOpaqueFields() []string {
64+
return []string{"channel_header", "signature_header"}
65+
}
66+
67+
func (h *Header) StaticallyOpaqueFieldProto(name string) (proto.Message, error) {
68+
switch name {
69+
case h.StaticallyOpaqueFields()[0]: // channel_header
70+
return &ChannelHeader{}, nil
71+
case h.StaticallyOpaqueFields()[1]: // signature_header
72+
return &SignatureHeader{}, nil
73+
default:
74+
return nil, fmt.Errorf("unknown header field: %s", name)
75+
}
76+
}
77+
78+
func (bd *BlockData) StaticallyOpaqueSliceFields() []string {
79+
return []string{"data"}
80+
}
81+
82+
func (bd *BlockData) StaticallyOpaqueSliceFieldProto(fieldName string, index int) (proto.Message, error) {
83+
if fieldName != bd.StaticallyOpaqueSliceFields()[0] {
84+
return nil, fmt.Errorf("not an opaque slice field: %s", fieldName)
85+
}
86+
87+
return &Envelope{}, nil
88+
}

protos/common/configtx.go

+82
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,92 @@ limitations under the License.
1616

1717
package common
1818

19+
import (
20+
"fmt"
21+
22+
"github.com/golang/protobuf/proto"
23+
)
24+
1925
func NewConfigGroup() *ConfigGroup {
2026
return &ConfigGroup{
2127
Groups: make(map[string]*ConfigGroup),
2228
Values: make(map[string]*ConfigValue),
2329
Policies: make(map[string]*ConfigPolicy),
2430
}
2531
}
32+
33+
func (cue *ConfigUpdateEnvelope) StaticallyOpaqueFields() []string {
34+
return []string{"config_update"}
35+
}
36+
37+
func (cue *ConfigUpdateEnvelope) StaticallyOpaqueFieldProto(name string) (proto.Message, error) {
38+
if name != cue.StaticallyOpaqueFields()[0] {
39+
return nil, fmt.Errorf("Not a marshaled field: %s", name)
40+
}
41+
return &ConfigUpdate{}, nil
42+
}
43+
44+
func (cs *ConfigSignature) StaticallyOpaqueFields() []string {
45+
return []string{"signature_header"}
46+
}
47+
48+
func (cs *ConfigSignature) StaticallyOpaqueFieldProto(name string) (proto.Message, error) {
49+
if name != cs.StaticallyOpaqueFields()[0] {
50+
return nil, fmt.Errorf("Not a marshaled field: %s", name)
51+
}
52+
return &SignatureHeader{}, nil
53+
}
54+
55+
func (c *Config) DynamicFields() []string {
56+
return []string{"channel_group"}
57+
}
58+
59+
func (c *Config) DynamicFieldProto(name string, base proto.Message) (proto.Message, error) {
60+
if name != c.DynamicFields()[0] {
61+
return nil, fmt.Errorf("Not a dynamic field: %s", name)
62+
}
63+
64+
cg, ok := base.(*ConfigGroup)
65+
if !ok {
66+
return nil, fmt.Errorf("Config must embed a config group as its dynamic field")
67+
}
68+
69+
return &DynamicChannelGroup{
70+
ConfigGroup: cg,
71+
}, nil
72+
}
73+
74+
func (c *ConfigUpdate) DynamicFields() []string {
75+
return []string{"read_set", "write_set"}
76+
}
77+
78+
func (c *ConfigUpdate) DynamicFieldProto(name string, base proto.Message) (proto.Message, error) {
79+
if name != c.DynamicFields()[0] && name != c.DynamicFields()[1] {
80+
return nil, fmt.Errorf("Not a dynamic field: %s", name)
81+
}
82+
83+
cg, ok := base.(*ConfigGroup)
84+
if !ok {
85+
return nil, fmt.Errorf("Expected base to be *ConfigGroup, got %T", base)
86+
}
87+
88+
return &DynamicChannelGroup{
89+
ConfigGroup: cg,
90+
}, nil
91+
}
92+
93+
func (cv *ConfigValue) VariablyOpaqueFields() []string {
94+
return []string{"value"}
95+
}
96+
97+
func (cv *ConfigValue) Underlying() proto.Message {
98+
return cv
99+
}
100+
101+
func (cg *ConfigGroup) DynamicMapFields() []string {
102+
return []string{"groups", "values"}
103+
}
104+
105+
func (cg *ConfigGroup) Underlying() proto.Message {
106+
return cg
107+
}

0 commit comments

Comments
 (0)