go_study/fabric-main/internal/pkg/gateway/diff.go

454 lines
12 KiB
Go

/*
Copyright 2021 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package gateway
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/protoutil"
)
type (
baseDifference struct {
namespace string
key string
}
readDifference struct {
*baseDifference
expected uint64
actual uint64
}
writeDifference struct {
*baseDifference
expected []byte
actual []byte
}
pvtHashDifference struct {
*writeDifference
}
metadataDifference struct {
*writeDifference
name string
}
resultDifference struct {
reads []*readDifference
writes []*writeDifference
metawrites []*metadataDifference
private []*pvtHashDifference
}
ccEvent struct {
chaincodeId string
name string
payload []byte
}
eventDifference struct {
expected *ccEvent
actual *ccEvent
}
response struct {
status int32
message string
payload []byte
}
responseDifference struct {
expected *response
actual *response
}
prpDifference struct {
results *resultDifference
response *responseDifference
event *eventDifference
}
readset map[string]uint64
writeset map[string][]byte
metaset map[string]writeset
readwriteset struct {
r readset
w writeset
p writeset
m metaset
}
nsRWsets map[string]readwriteset
)
func (rwd *resultDifference) addReadDiff(ns string, key string, expected uint64, actual uint64) {
rwd.reads = append(rwd.reads, &readDifference{
baseDifference: &baseDifference{
namespace: ns,
key: key,
},
expected: expected,
actual: actual,
})
}
func (rwd *resultDifference) addWriteDiff(ns string, key string, expected []byte, actual []byte) {
rwd.writes = append(rwd.writes, &writeDifference{
baseDifference: &baseDifference{
namespace: ns,
key: key,
},
expected: expected,
actual: actual,
})
}
func (rwd *resultDifference) addMetadataWriteDiff(ns string, key string, name string, expected []byte, actual []byte) {
rwd.metawrites = append(rwd.metawrites, &metadataDifference{
writeDifference: &writeDifference{
baseDifference: &baseDifference{
namespace: ns,
key: key,
},
expected: expected,
actual: actual,
},
name: name,
})
}
func (rwd *resultDifference) addPvtHashDiff(ns string, collection string, expected []byte, actual []byte) {
rwd.private = append(rwd.private, &pvtHashDifference{
writeDifference: &writeDifference{
baseDifference: &baseDifference{
namespace: ns,
key: collection,
},
expected: expected,
actual: actual,
},
})
}
func payloadDifference(payload1, payload2 []byte) (*prpDifference, error) {
prp1, err := protoutil.UnmarshalProposalResponsePayload(payload1)
if err != nil {
return nil, err
}
prp2, err := protoutil.UnmarshalProposalResponsePayload(payload2)
if err != nil {
return nil, err
}
ca1, err := protoutil.UnmarshalChaincodeAction(prp1.GetExtension())
if err != nil {
return nil, err
}
ca2, err := protoutil.UnmarshalChaincodeAction(prp2.GetExtension())
if err != nil {
return nil, err
}
rwDiff, err := rwsetDifference(ca1.GetResults(), ca2.GetResults())
if err != nil {
return nil, err
}
respDiff := responseDiff(ca1.GetResponse(), ca2.GetResponse())
evDiff, err := eventDiff(ca1.GetEvents(), ca2.GetEvents())
if err != nil {
return nil, err
}
return &prpDifference{
results: rwDiff,
response: respDiff,
event: evDiff,
}, nil
}
func rwsetDifference(rwset1, rwset2 []byte) (*resultDifference, error) {
if bytes.Equal(rwset1, rwset2) {
return nil, nil
}
txrw1, err := protoutil.UnmarshalTxReadWriteSet(rwset1)
if err != nil {
return nil, err
}
txrw2, err := protoutil.UnmarshalTxReadWriteSet(rwset2)
if err != nil {
return nil, err
}
summarySet := nsRWsets{}
rwDiff := &resultDifference{}
for _, txrw := range txrw1.GetNsRwset() {
reads := readset{}
writes := writeset{}
pvtHashes := writeset{}
metadata := metaset{}
kvrws, err := protoutil.UnmarshalKVRWSet(txrw.Rwset)
if err != nil {
return nil, err
}
for _, r := range kvrws.GetReads() {
reads[r.GetKey()] = r.GetVersion().GetBlockNum()
}
for _, w := range kvrws.GetWrites() {
writes[w.GetKey()] = w.GetValue()
}
for _, mw := range kvrws.GetMetadataWrites() {
entryset := writeset{}
for _, me := range mw.GetEntries() {
entryset[me.GetName()] = me.GetValue()
}
metadata[mw.GetKey()] = entryset
}
for _, chrws := range txrw.GetCollectionHashedRwset() {
pvtHashes[chrws.GetCollectionName()] = chrws.GetPvtRwsetHash()
}
summarySet[txrw.GetNamespace()] = readwriteset{r: reads, w: writes, m: metadata, p: pvtHashes}
}
for _, txrw := range txrw2.GetNsRwset() {
var reads readset
var writes writeset
var pvtHashes writeset
var metadata metaset
if rw, ok := summarySet[txrw.GetNamespace()]; ok {
reads = rw.r
writes = rw.w
metadata = rw.m
pvtHashes = rw.p
}
kvrws, err := protoutil.UnmarshalKVRWSet(txrw.GetRwset())
if err != nil {
return nil, err
}
for _, r := range kvrws.GetReads() {
block := reads[r.GetKey()] // missing entry will be represented by the zero value
if block != r.GetVersion().GetBlockNum() {
// state is at different version (or not present in rwset1 if block is zero)
rwDiff.addReadDiff(txrw.GetNamespace(), r.GetKey(), block, r.GetVersion().GetBlockNum())
}
delete(reads, r.GetKey())
}
for _, w := range kvrws.GetWrites() {
value := writes[w.GetKey()]
if !bytes.Equal(value, w.GetValue()) {
// state writes different value (or not present in rwset1 if value is nil)
rwDiff.addWriteDiff(txrw.GetNamespace(), w.GetKey(), value, w.GetValue())
}
delete(writes, w.GetKey())
}
for _, mw := range kvrws.GetMetadataWrites() {
expected := metadata[mw.GetKey()]
for _, e := range mw.GetEntries() {
value := expected[e.GetName()]
if !bytes.Equal(value, e.GetValue()) {
rwDiff.addMetadataWriteDiff(txrw.GetNamespace(), mw.GetKey(), e.GetName(), value, e.GetValue())
}
delete(expected, e.GetName())
}
}
for _, chrws := range txrw.GetCollectionHashedRwset() {
hash := pvtHashes[chrws.GetCollectionName()]
if !bytes.Equal(hash, chrws.GetPvtRwsetHash()) {
// state writes different value (or not present in rwset1 if value is nil)
rwDiff.addPvtHashDiff(txrw.GetNamespace(), chrws.GetCollectionName(), hash, chrws.GetPvtRwsetHash())
}
delete(pvtHashes, chrws.GetCollectionName())
}
}
// whatever is left in the summary set is present in rwset1 but not rwset2
for ns, rw := range summarySet {
for key, block := range rw.r {
rwDiff.addReadDiff(ns, key, block, 0)
}
for key, value := range rw.w {
rwDiff.addWriteDiff(ns, key, value, nil)
}
for key, entries := range rw.m {
for name, value := range entries {
rwDiff.addMetadataWriteDiff(ns, key, name, value, nil)
}
}
for coll, hash := range rw.p {
rwDiff.addPvtHashDiff(ns, coll, hash, nil)
}
}
return rwDiff, nil
}
func responseDiff(resp1, resp2 *peer.Response) *responseDifference {
if resp1.GetStatus() == resp2.GetStatus() &&
resp1.GetMessage() == resp2.GetMessage() &&
bytes.Equal(resp1.GetPayload(), resp2.GetPayload()) {
return nil
}
return &responseDifference{
expected: &response{
status: resp1.GetStatus(),
message: resp1.GetMessage(),
payload: resp1.GetPayload(),
},
actual: &response{
status: resp2.GetStatus(),
message: resp2.GetMessage(),
payload: resp2.GetPayload(),
},
}
}
func eventDiff(ev1, ev2 []byte) (*eventDifference, error) {
if bytes.Equal(ev1, ev2) {
return nil, nil
}
expected, err := protoutil.UnmarshalChaincodeEvents(ev1)
if err != nil {
return nil, err
}
actual, err := protoutil.UnmarshalChaincodeEvents(ev2)
if err != nil {
return nil, err
}
return &eventDifference{
expected: &ccEvent{
chaincodeId: expected.GetChaincodeId(),
name: expected.GetEventName(),
payload: expected.GetPayload(),
},
actual: &ccEvent{
chaincodeId: actual.GetChaincodeId(),
name: actual.GetEventName(),
payload: actual.GetPayload(),
},
}, nil
}
// returns key/value pairs for passing to the logger.Debugw function
func (rd *readDifference) info() []interface{} {
description := "read value mismatch"
if rd.expected == 0 {
description = "extraneous read"
} else if rd.actual == 0 {
description = "missing read"
}
return []interface{}{
"type", description,
"namespace", rd.namespace,
"key", rd.key,
"initial-endorser-value", fmt.Sprintf("%d", rd.expected),
"invoked-endorser-value", fmt.Sprintf("%d", rd.actual),
}
}
func (wd *writeDifference) info() []interface{} {
description := "write value mismatch"
if wd.expected == nil {
description = "extraneous write"
} else if wd.actual == nil {
description = "missing write"
}
return []interface{}{
"type", description,
"namespace", wd.namespace,
"key", wd.key,
"initial-endorser-value", string(wd.expected),
"invoked-endorser-value", string(wd.actual),
}
}
func (wd *pvtHashDifference) info() []interface{} {
return []interface{}{
"type", "private collection hash mismatch",
"namespace", wd.namespace,
"collection", wd.key,
"initial-endorser-hash", hex.EncodeToString(wd.expected),
"invoked-endorser-hash", hex.EncodeToString(wd.actual),
}
}
func (md *metadataDifference) info() []interface{} {
description := "write metadata mismatch"
if md.expected == nil {
description = "extraneous metadata write"
} else if md.actual == nil {
description = "missing metadata write"
}
var expected string
var actual string
if md.name == "VALIDATION_PARAMETER" {
// this is a SBE policy - unmarshall it
description += " (SBE policy)"
sbeA, err := protoutil.UnmarshalSignaturePolicy(md.expected)
if err != nil {
expected = fmt.Sprintf("Error unmarshalling SBE policy: %s", err)
} else {
expected = fmt.Sprintf("%v", sbeA)
}
sbeB, err := protoutil.UnmarshalSignaturePolicy(md.actual)
if err != nil {
actual = fmt.Sprintf("Error unmarshalling SBE policy: %s", err)
} else {
actual = fmt.Sprintf("%v", sbeB)
}
} else {
expected = string(md.expected)
actual = string(md.actual)
}
return []interface{}{
"type", description,
"namespace", md.namespace,
"key", md.key,
"name", md.name,
"initial-endorser-value", expected,
"invoked-endorser-value", actual,
}
}
func (ev *eventDifference) info() []interface{} {
return []interface{}{
"type", "chaincode event mismatch",
"initial-endorser-event", fmt.Sprintf("chaincodeId: %s, name: %s, value: %s", ev.expected.chaincodeId, ev.expected.name, ev.expected.payload),
"invoked-endorser-event", fmt.Sprintf("chaincodeId: %s, name: %s, value: %s", ev.actual.chaincodeId, ev.actual.name, ev.actual.payload),
}
}
func (resp *responseDifference) info() []interface{} {
return []interface{}{
"type", "chaincode response mismatch",
"initial-endorser-response", fmt.Sprintf("status: %d, message: %s, payload: %s", resp.expected.status, resp.expected.message, resp.expected.payload),
"invoked-endorser-response", fmt.Sprintf("status: %d, message: %s, payload: %s", resp.actual.status, resp.actual.message, resp.actual.payload),
}
}
func (diff *prpDifference) details() [][]interface{} {
var details [][]interface{}
if diff.results != nil {
for _, rd := range diff.results.reads {
details = append(details, rd.info())
}
for _, wd := range diff.results.writes {
details = append(details, wd.info())
}
for _, md := range diff.results.metawrites {
details = append(details, md.info())
}
for _, pd := range diff.results.private {
details = append(details, pd.info())
}
}
if diff.event != nil {
details = append(details, diff.event.info())
}
if diff.response != nil {
details = append(details, diff.response.info())
}
return details
}