/* 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 }