From 4d77a8c672fcc368325d656b2ca288c0678df1a2 Mon Sep 17 00:00:00 2001 From: Artem Barger Date: Tue, 27 Dec 2016 16:14:50 +0200 Subject: [PATCH] [FAB-1038] Rework commiter to be more general As part of the work needed for FAB-1038 to complete, this commit abstracts out transaction validation entity and extacts related code out of noopssinglechain/client.go. Change-Id: I6949c99abab6f58472dd8e95929201e2a472f13d Signed-off-by: Artem Barger --- core/committer/noopssinglechain/client.go | 120 ++---------- .../committer/txvalidator/txvalidator_test.go | 67 +++++++ core/committer/txvalidator/validator.go | 177 ++++++++++++++++++ protos/utils/blockutils.go | 17 +- 4 files changed, 270 insertions(+), 111 deletions(-) create mode 100644 core/committer/txvalidator/txvalidator_test.go create mode 100644 core/committer/txvalidator/validator.go diff --git a/core/committer/noopssinglechain/client.go b/core/committer/noopssinglechain/client.go index d30bc1c46d2..e812d6d1192 100644 --- a/core/committer/noopssinglechain/client.go +++ b/core/committer/noopssinglechain/client.go @@ -17,28 +17,22 @@ limitations under the License. package noopssinglechain import ( - "fmt" "math" "time" "github.com/golang/protobuf/proto" - "github.com/hyperledger/fabric/core/chaincode" "github.com/hyperledger/fabric/core/committer" - "github.com/hyperledger/fabric/core/util" + "github.com/hyperledger/fabric/core/committer/txvalidator" + "github.com/hyperledger/fabric/core/peer" "github.com/hyperledger/fabric/events/producer" + gossip_proto "github.com/hyperledger/fabric/gossip/proto" + "github.com/hyperledger/fabric/gossip/service" "github.com/hyperledger/fabric/protos/common" "github.com/hyperledger/fabric/protos/orderer" - putils "github.com/hyperledger/fabric/protos/utils" "github.com/op/go-logging" "github.com/spf13/viper" "golang.org/x/net/context" "google.golang.org/grpc" - - "github.com/hyperledger/fabric/core/ledger" - "github.com/hyperledger/fabric/core/peer" - gossip_proto "github.com/hyperledger/fabric/gossip/proto" - "github.com/hyperledger/fabric/gossip/service" - pb "github.com/hyperledger/fabric/protos/peer" ) var logger *logging.Logger // package-level logger @@ -178,56 +172,6 @@ func (d *DeliverService) seekLatestFromCommitter(height uint64) error { }) } -func isTxValidForVscc(payload *common.Payload, envBytes []byte) error { - // TODO: Extract the VSCC/policy from LCCC as soon as this is ready - vscc := "vscc" - - chainName := payload.Header.ChainHeader.ChainID - if chainName == "" { - err := fmt.Errorf("transaction header does not contain an chain ID") - logger.Errorf("%s", err) - return err - } - - txid := "N/A" // FIXME: is that appropriate? - - // build arguments for VSCC invocation - // args[0] - function name (not used now) - // args[1] - serialized Envelope - args := [][]byte{[]byte(""), envBytes} - - // create VSCC invocation proposal - vsccCis := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: vscc}, CtorMsg: &pb.ChaincodeInput{Args: args}}} - prop, err := putils.CreateProposalFromCIS(txid, chainName, vsccCis, []byte("")) - if err != nil { - logger.Errorf("Cannot create a proposal to invoke VSCC, err %s\n", err) - return err - } - - // get context for the chaincode execution - var txsim ledger.TxSimulator - lgr := peer.GetLedger(chainName) - txsim, err = lgr.NewTxSimulator() - if err != nil { - logger.Errorf("Cannot obtain tx simulator, err %s\n", err) - return err - } - defer txsim.Done() - ctxt := context.WithValue(context.Background(), chaincode.TXSimulatorKey, txsim) - - version := util.GetSysCCVersion() - cccid := chaincode.NewCCContext(chainName, vscc, version, txid, true, prop) - - // invoke VSCC - _, _, err = chaincode.ExecuteChaincode(ctxt, cccid, args) - if err != nil { - logger.Errorf("VSCC check failed for transaction, error %s", err) - return err - } - - return nil -} - func (d *DeliverService) readUntilClose() { for { msg, err := d.client.Recv() @@ -244,63 +188,26 @@ func (d *DeliverService) readUntilClose() { logger.Warning("Got error ", t) case *orderer.DeliverResponse_Block: seqNum := t.Block.Header.Number - block := &common.Block{} - block.Header = t.Block.Header - - // Copy and initialize peer metadata - putils.CopyBlockMetadata(t.Block, block) - block.Data = &common.BlockData{} - for _, d := range t.Block.Data.Data { - if d != nil { - if env, err := putils.GetEnvelopeFromBlock(d); err != nil { - fmt.Printf("Error getting tx from block(%s)\n", err) - } else if env != nil { - // validate the transaction: here we check that the transaction - // is properly formed, properly signed and that the security - // chain binding proposal to endorsements to tx holds. We do - // NOT check the validity of endorsements, though. That's a - // job for VSCC below - payload, _, err := peer.ValidateTransaction(env) - if err != nil { - // TODO: this code needs to receive a bit more attention and discussion: - // it's not clear what it means if a transaction which causes a failure - // in validation is just dropped on the floor - logger.Errorf("Invalid transaction, error %s", err) - } else { - //the payload is used to get headers - err = isTxValidForVscc(payload, d) - if err != nil { - // TODO: this code needs to receive a bit more attention and discussion: - // it's not clear what it means if a transaction which causes a failure - // in validation is just dropped on the floor - logger.Errorf("isTxValidForVscc returned error %s", err) - continue - } - - if t, err := proto.Marshal(env); err == nil { - block.Data.Data = append(block.Data.Data, t) - } else { - fmt.Printf("Cannot marshal transactoins %s\n", err) - } - } - } else { - logger.Warning("Nil tx from block") - } - } - } + + // Create new transactions validator + validator := txvalidator.NewTxValidator(peer.GetLedger(d.chainID)) + // Validate and mark invalid transactions + validator.Validate(t.Block) numberOfPeers := len(service.GetGossipService().GetPeers()) // Create payload with a block received - payload := createPayload(seqNum, block) + payload := createPayload(seqNum, t.Block) // Use payload to create gossip message gossipMsg := createGossipMsg(payload) + logger.Debugf("Adding payload locally, buffer seqNum = [%d], peers number [%d]", seqNum, numberOfPeers) // Add payload to local state payloads buffer service.GetGossipService().AddPayload(d.chainID, payload) + // Gossip messages with other nodes logger.Debugf("Gossiping block [%d], peers number [%d]", seqNum, numberOfPeers) service.GetGossipService().Gossip(gossipMsg) - if err = producer.SendProducerBlockEvent(block); err != nil { + if err = producer.SendProducerBlockEvent(t.Block); err != nil { logger.Errorf("Error sending block event %s", err) } @@ -319,6 +226,7 @@ func createGossipMsg(payload *gossip_proto.Payload) *gossip_proto.GossipMessage Payload: payload, }, }, + Tag: gossip_proto.GossipMessage_EMPTY, } return gossipMsg } diff --git a/core/committer/txvalidator/txvalidator_test.go b/core/committer/txvalidator/txvalidator_test.go new file mode 100644 index 00000000000..0990d4ae6fb --- /dev/null +++ b/core/committer/txvalidator/txvalidator_test.go @@ -0,0 +1,67 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package txvalidator + +import ( + "os" + "testing" + + "github.com/hyperledger/fabric/core/ledger/kvledger" + "github.com/hyperledger/fabric/core/ledger/testutil" + "github.com/hyperledger/fabric/core/ledger/util" + "github.com/hyperledger/fabric/protos/common" + pb "github.com/hyperledger/fabric/protos/peer" + "github.com/stretchr/testify/assert" +) + +type mockVsccValidator struct { +} + +func (v *mockVsccValidator) VSCCValidateTx(payload *common.Payload, envBytes []byte) error { + return nil +} + +func TestKVLedgerBlockStorage(t *testing.T) { + conf := kvledger.NewConf("/tmp/tests/ledger/", 0) + defer os.RemoveAll("/tmp/tests/ledger/") + + ledger, _ := kvledger.NewKVLedger(conf) + defer ledger.Close() + + validator := &txValidator{ledger, &mockVsccValidator{}} + + bcInfo, _ := ledger.GetBlockchainInfo() + testutil.AssertEquals(t, bcInfo, &pb.BlockchainInfo{ + Height: 0, CurrentBlockHash: nil, PreviousBlockHash: nil}) + + simulator, _ := ledger.NewTxSimulator() + simulator.SetState("ns1", "key1", []byte("value1")) + simulator.SetState("ns1", "key2", []byte("value2")) + simulator.SetState("ns1", "key3", []byte("value3")) + simulator.Done() + + simRes, _ := simulator.GetTxSimulationResults() + block := testutil.ConstructBlock(t, [][]byte{simRes}, true) + + validator.Validate(block) + + txsfltr := util.NewFilterBitArrayFromBytes(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]) + + assert.True(t, !txsfltr.IsSet(uint(0))) + assert.True(t, !txsfltr.IsSet(uint(1))) + assert.True(t, !txsfltr.IsSet(uint(2))) +} diff --git a/core/committer/txvalidator/validator.go b/core/committer/txvalidator/validator.go new file mode 100644 index 00000000000..849960284f7 --- /dev/null +++ b/core/committer/txvalidator/validator.go @@ -0,0 +1,177 @@ +/* +Copyright IBM Corp. 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package txvalidator + +import ( + "context" + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode" + "github.com/hyperledger/fabric/core/ledger" + ledgerUtil "github.com/hyperledger/fabric/core/ledger/util" + "github.com/hyperledger/fabric/core/peer" + coreUtil "github.com/hyperledger/fabric/core/util" + "github.com/hyperledger/fabric/protos/common" + "github.com/hyperledger/fabric/protos/utils" + "github.com/op/go-logging" +) + +//Validator interface which defines API to validate block transactions +// and return the bit array mask indicating invalid transactions which +// didn't pass validation. +type Validator interface { + Validate(block *common.Block) +} + +// private interface to decouple tx validator +// and vscc execution, in order to increase +// testability of txValidator +type vsccValidator interface { + VSCCValidateTx(payload *common.Payload, envBytes []byte) error +} + +// vsccValidator implementation which used to call +// vscc chaincode and validate block transactions +type vsccValidatorImpl struct { + ledger ledger.ValidatedLedger +} + +// implementation of Validator interface, keeps +// reference to the ledger to enable tx simulation +// and execution of vscc +type txValidator struct { + ledger ledger.ValidatedLedger + vscc vsccValidator +} + +var logger *logging.Logger // package-level logger + +func init() { + // Init logger with module name + logger = logging.MustGetLogger("txvalidator") +} + +// NewTxValidator creates new transactions validator +func NewTxValidator(ledger ledger.ValidatedLedger) Validator { + // Encapsulates interface implementation + return &txValidator{ledger, &vsccValidatorImpl{ledger}} +} + +func (v *txValidator) Validate(block *common.Block) { + txsfltr := ledgerUtil.NewFilterBitArray(uint(len(block.Data.Data))) + for tIdx, d := range block.Data.Data { + if d != nil { + if env, err := utils.GetEnvelopeFromBlock(d); err != nil { + logger.Warningf("Error getting tx from block(%s)", err) + } else if env != nil { + // validate the transaction: here we check that the transaction + // is properly formed, properly signed and that the security + // chain binding proposal to endorsements to tx holds. We do + // NOT check the validity of endorsements, though. That's a + // job for VSCC below + if payload, _, err := peer.ValidateTransaction(env); err != nil { + // TODO: this code needs to receive a bit more attention and discussion: + // it's not clear what it means if a transaction which causes a failure + // in validation is just dropped on the floor + logger.Errorf("Invalid transaction with index %s, error %s", tIdx, err) + txsfltr.Set(uint(tIdx)) + } else { + //the payload is used to get headers + if err = v.vscc.VSCCValidateTx(payload, d); err != nil { + // TODO: this code needs to receive a bit more attention and discussion: + // it's not clear what it means if a transaction which causes a failure + // in validation is just dropped on the floor + txID := payload.Header.ChainHeader.TxID + logger.Errorf("isTxValidForVscc for transaction txId = %s returned error %s", txID, err) + txsfltr.Set(uint(tIdx)) + continue + } + + if t, err := proto.Marshal(env); err == nil { + block.Data.Data = append(block.Data.Data, t) + } else { + logger.Warningf("Cannot marshal transactoins %s", err) + txsfltr.Set(uint(tIdx)) + } + } + } else { + logger.Warning("Nil tx from block") + txsfltr.Set(uint(tIdx)) + } + } + } + // Initialize metadata structure + utils.InitBlockMetadata(block) + // Serialize invalid transaction bit array into block metadata field + block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsfltr.ToBytes() +} + +func (v *vsccValidatorImpl) VSCCValidateTx(payload *common.Payload, envBytes []byte) error { + // Chain ID + chainID := payload.Header.ChainHeader.ChainID + if chainID == "" { + err := fmt.Errorf("transaction header does not contain an chain ID") + logger.Errorf("%s", err) + return err + } + + // Get transaction id + txid := payload.Header.ChainHeader.TxID + if txid == "" { + err := fmt.Errorf("transaction header does not contain transaction ID") + logger.Errorf("%s", err) + return err + } + + // build arguments for VSCC invocation + // args[0] - function name (not used now) + // args[1] - serialized Envelope + args := [][]byte{[]byte(""), envBytes} + + // get context for the chaincode execution + lgr := v.ledger + txsim, err := lgr.NewTxSimulator() + if err != nil { + logger.Errorf("Cannot obtain tx simulator txid=%s, err %s", txid, err) + return err + } + defer txsim.Done() + ctxt := context.WithValue(context.Background(), chaincode.TXSimulatorKey, txsim) + + // Extracting vscc from lccc + /* + data, err := chaincode.GetChaincodeDataFromLCCC(ctxt, txid, nil, chainID, "vscc") + if err != nil { + logger.Errorf("Unable to get chaincode data from LCCC for txid %s, due to %s", txid, err) + return err + } + */ + + // Get chaincode version + version := coreUtil.GetSysCCVersion() + cccid := chaincode.NewCCContext(chainID, "vscc", version, txid, true, nil) + + // invoke VSCC + _, _, err = chaincode.ExecuteChaincode(ctxt, cccid, args) + if err != nil { + logger.Errorf("VSCC check failed for transaction txid=%s, error %s", txid, err) + return err + } + + return nil +} diff --git a/protos/utils/blockutils.go b/protos/utils/blockutils.go index f4462cf486b..45dc453632b 100644 --- a/protos/utils/blockutils.go +++ b/protos/utils/blockutils.go @@ -55,11 +55,18 @@ func GetBlockFromBlockBytes(blockBytes []byte) (*cb.Block, error) { // CopyBlockMetadata copies metadata from one block into another func CopyBlockMetadata(src *cb.Block, dst *cb.Block) { dst.Metadata = src.Metadata - if dst.Metadata == nil { - dst.Metadata = &cb.BlockMetadata{Metadata: [][]byte{[]byte{}, []byte{}, []byte{}}} - } else if len(dst.Metadata.Metadata) < int(cb.BlockMetadataIndex_TRANSACTIONS_FILTER+1) { - for i := int(len(dst.Metadata.Metadata)); i <= int(cb.BlockMetadataIndex_TRANSACTIONS_FILTER); i++ { - dst.Metadata.Metadata = append(dst.Metadata.Metadata, []byte{}) + // Once copied initialize with rest of the + // required metadata positions. + InitBlockMetadata(dst) +} + +// CopyBlockMetadata copies metadata from one block into another +func InitBlockMetadata(block *cb.Block) { + if block.Metadata == nil { + block.Metadata = &cb.BlockMetadata{Metadata: [][]byte{[]byte{}, []byte{}, []byte{}}} + } else if len(block.Metadata.Metadata) < int(cb.BlockMetadataIndex_TRANSACTIONS_FILTER+1) { + for i := int(len(block.Metadata.Metadata)); i <= int(cb.BlockMetadataIndex_TRANSACTIONS_FILTER); i++ { + block.Metadata.Metadata = append(block.Metadata.Metadata, []byte{}) } } }