Skip to content

Commit

Permalink
[FAB-6669] forbid Tx with same ID as other in blck
Browse files Browse the repository at this point in the history
This change set makes sure that if there is a tx with the same txid as a
previous tx in the block, the tx is marked as a duplicate and invalidated.
This change set is not backward compatible and so a new application
capability is introduced.

Change-Id: Ia1490079d716c5d51b23af0765bffed27b9b843c
Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
  • Loading branch information
ale-linux committed Oct 27, 2017
1 parent a7445e7 commit 69fd2b1
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 33 deletions.
6 changes: 6 additions & 0 deletions common/capabilities/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ func (ap *ApplicationProvider) HasCapability(capability string) bool {
func (ap *ApplicationProvider) LifecycleViaConfig() bool {
return ap.v11
}

// ForbidDuplicateTXIdInBlock specifies whether two transactions with the same TXId are permitted
// in the same block or whether we mark the second one as TxValidationCode_DUPLICATE_TXID
func (ap *ApplicationProvider) ForbidDuplicateTXIdInBlock() bool {
return ap.v11
}
4 changes: 4 additions & 0 deletions common/channelconfig/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ type ChannelCapabilities interface {
type ApplicationCapabilities interface {
// Supported returns an error if there are unknown capabilities in this channel which are required
Supported() error

// ForbidDuplicateTXIdInBlock specifies whether two transactions with the same TXId are permitted
// in the same block or whether we mark the second one as TxValidationCode_DUPLICATE_TXID
ForbidDuplicateTXIdInBlock() bool
}

// OrdererCapabilities defines the capabilities for the orderer portion of a channel
Expand Down
6 changes: 3 additions & 3 deletions common/ledger/testutil/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func ConstructBlockWithTxid(t *testing.T, blockNum uint64, previousHash []byte,
}
envs = append(envs, env)
}
return newBlock(envs, blockNum, previousHash)
return NewBlock(envs, blockNum, previousHash)
}

// ConstructBlock constructs a single block
Expand All @@ -123,7 +123,7 @@ func ConstructBlock(t *testing.T, blockNum uint64, previousHash []byte, simulati
}
envs = append(envs, env)
}
return newBlock(envs, blockNum, previousHash)
return NewBlock(envs, blockNum, previousHash)
}

//ConstructTestBlock constructs a single block with random contents
Expand Down Expand Up @@ -155,7 +155,7 @@ func ConstructBytesProposalResponsePayload(version string, simulationResults []b
return ptestutils.ConstructBytesProposalResponsePayload(util.GetTestChainID(), ccid, nil, simulationResults)
}

func newBlock(env []*common.Envelope, blockNum uint64, previousHash []byte) *common.Block {
func NewBlock(env []*common.Envelope, blockNum uint64, previousHash []byte) *common.Block {
block := common.NewBlock(blockNum, previousHash)
for i := 0; i < len(env); i++ {
txEnvBytes, _ := proto.Marshal(env[i])
Expand Down
20 changes: 20 additions & 0 deletions common/mocks/config/application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright IBM Corp. 2017 All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package config

type MockApplicationCapabilities struct {
SupportedRv error
ForbidDuplicateTXIdInBlockRv bool
}

func (mac *MockApplicationCapabilities) Supported() error {
return mac.SupportedRv
}

func (mac *MockApplicationCapabilities) ForbidDuplicateTXIdInBlock() bool {
return mac.ForbidDuplicateTXIdInBlockRv
}
88 changes: 86 additions & 2 deletions core/committer/txvalidator/txvalidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/configtx/test"
"github.com/hyperledger/fabric/common/ledger/testutil"
"github.com/hyperledger/fabric/common/mocks/config"
util2 "github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/common/sysccprovider"
ledger2 "github.com/hyperledger/fabric/core/ledger"
Expand Down Expand Up @@ -60,7 +61,7 @@ func testValidationWithNTXes(t *testing.T, ledger ledger2.PeerLedger, gbHash []b
vcs := struct {
*mocktxvalidator.Support
*semaphore.Weighted
}{&mocktxvalidator.Support{LedgerVal: ledger}, semaphore.NewWeighted(10)}
}{&mocktxvalidator.Support{LedgerVal: ledger, ACVal: &config.MockApplicationCapabilities{}}, semaphore.NewWeighted(10)}
tValidator := &txValidator{vcs, mockVsccValidator}

bcInfo, _ := ledger.GetBlockchainInfo()
Expand Down Expand Up @@ -103,6 +104,89 @@ func testValidationWithNTXes(t *testing.T, ledger ledger2.PeerLedger, gbHash []b
*/
}

func TestDetectTXIdDuplicates(t *testing.T) {
txids := []string{"", "1", "2", "3", "", "2", ""}
txsfltr := ledgerUtil.NewTxValidationFlags(len(txids))
markTXIdDuplicates(txids, txsfltr)
assert.True(t, txsfltr.IsSetTo(0, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(1, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(2, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(3, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(4, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(5, peer.TxValidationCode_DUPLICATE_TXID))
assert.True(t, txsfltr.IsSetTo(6, peer.TxValidationCode_VALID))

txids = []string{"", "1", "2", "3", "", "21", ""}
txsfltr = ledgerUtil.NewTxValidationFlags(len(txids))
markTXIdDuplicates(txids, txsfltr)
assert.True(t, txsfltr.IsSetTo(0, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(1, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(2, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(3, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(4, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(5, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(6, peer.TxValidationCode_VALID))
}

func TestBlockValidationDuplicateTXId(t *testing.T) {
viper.Set("peer.fileSystemPath", "/tmp/fabric/txvalidatortest")
ledgermgmt.InitializeTestEnv()
defer ledgermgmt.CleanupTestEnv()

gb, _ := test.MakeGenesisBlock("TestLedger")
gbHash := gb.Header.Hash()
ledger, _ := ledgermgmt.CreateLedger(gb)
defer ledger.Close()

txid := util2.GenerateUUID()
simulator, _ := ledger.NewTxSimulator(txid)
simulator.SetState("ns1", "key1", []byte("value1"))
simulator.SetState("ns1", "key2", []byte("value2"))
simulator.SetState("ns1", "key3", []byte("value3"))
simulator.Done()

simRes, _ := simulator.GetTxSimulationResults()
pubSimulationResBytes, _ := simRes.GetPubSimulationBytes()
_, err := testutil.ConstructBytesProposalResponsePayload("v1", pubSimulationResBytes)
if err != nil {
t.Fatalf("Could not construct ProposalResponsePayload bytes, err: %s", err)
}

mockVsccValidator := &validator.MockVsccValidator{}
acv := &config.MockApplicationCapabilities{}
vcs := struct {
*mocktxvalidator.Support
*semaphore.Weighted
}{&mocktxvalidator.Support{LedgerVal: ledger, ACVal: acv}, semaphore.NewWeighted(10)}
tValidator := &txValidator{vcs, mockVsccValidator}

bcInfo, _ := ledger.GetBlockchainInfo()
testutil.AssertEquals(t, bcInfo, &common.BlockchainInfo{
Height: 1, CurrentBlockHash: gbHash, PreviousBlockHash: nil})

envs := []*common.Envelope{}
env, _, err := testutil.ConstructTransaction(t, pubSimulationResBytes, "", true)
envs = append(envs, env)
envs = append(envs, env)
block := testutil.NewBlock(envs, 1, gbHash)

tValidator.Validate(block)

txsfltr := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])

assert.True(t, txsfltr.IsSetTo(0, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(1, peer.TxValidationCode_VALID))

acv.ForbidDuplicateTXIdInBlockRv = true

tValidator.Validate(block)

txsfltr = util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])

assert.True(t, txsfltr.IsSetTo(0, peer.TxValidationCode_VALID))
assert.True(t, txsfltr.IsSetTo(1, peer.TxValidationCode_DUPLICATE_TXID))
}

func TestBlockValidation(t *testing.T) {
viper.Set("peer.fileSystemPath", "/tmp/fabric/txvalidatortest")
ledgermgmt.InitializeTestEnv()
Expand Down Expand Up @@ -160,7 +244,7 @@ func TestNewTxValidator_DuplicateTransactions(t *testing.T) {
vcs := struct {
*mocktxvalidator.Support
*semaphore.Weighted
}{&mocktxvalidator.Support{LedgerVal: ledger}, semaphore.NewWeighted(10)}
}{&mocktxvalidator.Support{LedgerVal: ledger, ACVal: &config.MockApplicationCapabilities{}}, semaphore.NewWeighted(10)}
tValidator := &txValidator{vcs, &validator.MockVsccValidator{}}

// Create simple endorsement transaction
Expand Down
37 changes: 35 additions & 2 deletions core/committer/txvalidator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/op/go-logging"

"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
)

Expand All @@ -52,6 +53,9 @@ type Support interface {
// GetMSPIDs returns the IDs for the application MSPs
// that have been defined in the channel
GetMSPIDs(cid string) []string

// Capabilities defines the capabilities for the application portion of this channel
Capabilities() channelconfig.ApplicationCapabilities
}

//Validator interface which defines API to validate block transactions
Expand Down Expand Up @@ -138,6 +142,7 @@ type blockValidationResult struct {
txsChaincodeName *sysccprovider.ChaincodeInstance
txsUpgradedChaincode *sysccprovider.ChaincodeInstance
err error
txid string
}

// NewTxValidator creates new transactions validator
Expand Down Expand Up @@ -187,6 +192,8 @@ func (v *txValidator) Validate(block *common.Block) error {
txsChaincodeNames := make(map[int]*sysccprovider.ChaincodeInstance)
// upgradedChaincodes records all the chaincodes that are upgraded in a block
txsUpgradedChaincodes := make(map[int]*sysccprovider.ChaincodeInstance)
// array of txids
txidArray := make([]string, len(block.Data.Data))

results := make(chan *blockValidationResult)
go func() {
Expand Down Expand Up @@ -240,6 +247,7 @@ func (v *txValidator) Validate(block *common.Block) error {
if res.txsUpgradedChaincode != nil {
txsUpgradedChaincodes[res.tIdx] = res.txsUpgradedChaincode
}
txidArray[res.tIdx] = res.txid
}
}
}
Expand All @@ -251,6 +259,12 @@ func (v *txValidator) Validate(block *common.Block) error {
return err
}

// if we operate with this capability, we mark invalid any transaction that has a txid
// which is equal to that of a previous tx in this block
if v.support.Capabilities().ForbidDuplicateTXIdInBlock() {
markTXIdDuplicates(txidArray, txsfltr)
}

// if we're here, all workers have completed validation and
// no error was reported; we set the tx filter and return
// success
Expand All @@ -265,11 +279,30 @@ func (v *txValidator) Validate(block *common.Block) error {
return nil
}

func markTXIdDuplicates(txids []string, txsfltr ledgerUtil.TxValidationFlags) {
txidMap := make(map[string]struct{})

for id, txid := range txids {
if txid == "" {
continue
}

_, in := txidMap[txid]
if in {
logger.Error("Duplicate txid", txid, "found, skipping")
txsfltr.SetFlag(id, peer.TxValidationCode_DUPLICATE_TXID)
} else {
txidMap[txid] = struct{}{}
}
}
}

func validateTx(req *blockValidationRequest, results chan<- *blockValidationResult) {
block := req.block
d := req.d
tIdx := req.tIdx
v := req.v
txID := ""

if d == nil {
results <- &blockValidationResult{
Expand Down Expand Up @@ -332,7 +365,7 @@ func validateTx(req *blockValidationRequest, results chan<- *blockValidationResu

if common.HeaderType(chdr.Type) == common.HeaderType_ENDORSER_TRANSACTION {
// Check duplicate transactions
txID := chdr.TxId
txID = chdr.TxId
if _, err := v.support.Ledger().GetTransactionByID(txID); err == nil {
logger.Error("Duplicate transaction found, ", txID, ", skipping")
results <- &blockValidationResult{
Expand All @@ -346,7 +379,6 @@ func validateTx(req *blockValidationRequest, results chan<- *blockValidationResu
logger.Debug("Validating transaction vscc tx validate")
err, cde := v.vscc.VSCCValidateTx(payload, d, env)
if err != nil {
txID := txID
logger.Errorf("VSCCValidateTx for transaction txId = %s returned error %s", txID, err)
switch err.(type) {
case *VSCCExecutionFailureError:
Expand Down Expand Up @@ -430,6 +462,7 @@ func validateTx(req *blockValidationRequest, results chan<- *blockValidationResu
txsChaincodeName: txsChaincodeName,
txsUpgradedChaincode: txsUpgradedChaincode,
validationCode: peer.TxValidationCode_VALID,
txid: txID,
}
return
} else {
Expand Down
34 changes: 8 additions & 26 deletions core/committer/txvalidator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
ctxt "github.com/hyperledger/fabric/common/configtx/test"
ledger2 "github.com/hyperledger/fabric/common/ledger"
"github.com/hyperledger/fabric/common/ledger/testutil"
mockconfig "github.com/hyperledger/fabric/common/mocks/config"
"github.com/hyperledger/fabric/common/mocks/scc"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/chaincode/shim"
Expand All @@ -36,6 +37,7 @@ import (
"github.com/hyperledger/fabric/core/ledger/ledgermgmt"
lutils "github.com/hyperledger/fabric/core/ledger/util"
"github.com/hyperledger/fabric/core/mocks/ccprovider"
mocktxvalidator "github.com/hyperledger/fabric/core/mocks/txvalidator"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/msp/mgmt"
"github.com/hyperledger/fabric/msp/mgmt/testtools"
Expand All @@ -61,9 +63,9 @@ func setupLedgerAndValidator(t *testing.T) (ledger.PeerLedger, Validator) {
theLedger, err := ledgermgmt.CreateLedger(gb)
assert.NoError(t, err)
vcs := struct {
*mockSupport
*mocktxvalidator.Support
*semaphore.Weighted
}{&mockSupport{l: theLedger}, semaphore.NewWeighted(10)}
}{&mocktxvalidator.Support{LedgerVal: theLedger, ACVal: &mockconfig.MockApplicationCapabilities{}}, semaphore.NewWeighted(10)}
theValidator := NewTxValidator(vcs)

return theLedger, theValidator
Expand Down Expand Up @@ -142,26 +144,6 @@ func putCCInfo(theLedger ledger.PeerLedger, ccname string, policy []byte, t *tes
putCCInfoWithVSCCAndVer(theLedger, ccname, "vscc", ccVersion, policy, t)
}

type mockSupport struct {
l ledger.PeerLedger
}

func (m *mockSupport) Ledger() ledger.PeerLedger {
return m.l
}

func (m *mockSupport) MSPManager() msp.MSPManager {
return mgmt.GetManagerForChain(util.GetTestChainID())
}

func (m *mockSupport) Apply(configtx *common.ConfigEnvelope) error {
return nil
}

func (m *mockSupport) GetMSPIDs(cid string) []string {
return []string{"DEFAULT"}
}

func assertInvalid(block *common.Block, t *testing.T, code peer.TxValidationCode) {
txsFilter := lutils.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
assert.True(t, txsFilter.IsInvalid(0))
Expand Down Expand Up @@ -568,9 +550,9 @@ func (exec *mockQueryExecutor) Done() {
func TestLedgerIsNoAvailable(t *testing.T) {
theLedger := new(mockLedger)
vcs := struct {
*mockSupport
*mocktxvalidator.Support
*semaphore.Weighted
}{&mockSupport{l: theLedger}, semaphore.NewWeighted(10)}
}{&mocktxvalidator.Support{LedgerVal: theLedger, ACVal: &mockconfig.MockApplicationCapabilities{}}, semaphore.NewWeighted(10)}
validator := NewTxValidator(vcs)

ccID := "mycc"
Expand All @@ -596,9 +578,9 @@ func TestLedgerIsNoAvailable(t *testing.T) {
func TestValidationInvalidEndorsing(t *testing.T) {
theLedger := new(mockLedger)
vcs := struct {
*mockSupport
*mocktxvalidator.Support
*semaphore.Weighted
}{&mockSupport{l: theLedger}, semaphore.NewWeighted(10)}
}{&mocktxvalidator.Support{LedgerVal: theLedger, ACVal: &mockconfig.MockApplicationCapabilities{}}, semaphore.NewWeighted(10)}
validator := NewTxValidator(vcs)

ccID := "mycc"
Expand Down
6 changes: 6 additions & 0 deletions core/mocks/txvalidator/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package support

import (
"github.com/hyperledger/fabric/common/channelconfig"
mockpolicies "github.com/hyperledger/fabric/common/mocks/policies"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/core/ledger"
Expand All @@ -28,6 +29,11 @@ type Support struct {
LedgerVal ledger.PeerLedger
MSPManagerVal msp.MSPManager
ApplyVal error
ACVal channelconfig.ApplicationCapabilities
}

func (ms *Support) Capabilities() channelconfig.ApplicationCapabilities {
return ms.ACVal
}

// Ledger returns LedgerVal
Expand Down

0 comments on commit 69fd2b1

Please sign in to comment.