Commit 69fd2b18 authored by Alessandro Sorniotti's avatar Alessandro Sorniotti
Browse files

[FAB-6669] forbid Tx with same ID as other in blck



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: default avatarAlessandro Sorniotti <ale.linux@sopit.net>
parent a7445e70
......@@ -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
}
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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])
......
/*
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
}
......@@ -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"
......@@ -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()
......@@ -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()
......@@ -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
......
......@@ -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"
)
......@@ -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
......@@ -138,6 +142,7 @@ type blockValidationResult struct {
txsChaincodeName *sysccprovider.ChaincodeInstance
txsUpgradedChaincode *sysccprovider.ChaincodeInstance
err error
txid string
}
// NewTxValidator creates new transactions validator
......@@ -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() {
......@@ -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
}
}
}
......@@ -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
......@@ -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{
......@@ -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{
......@@ -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:
......@@ -430,6 +462,7 @@ func validateTx(req *blockValidationRequest, results chan<- *blockValidationResu
txsChaincodeName: txsChaincodeName,
txsUpgradedChaincode: txsUpgradedChaincode,
validationCode: peer.TxValidationCode_VALID,
txid: txID,
}
return
} else {
......
......@@ -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"
......@@ -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"
......@@ -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
......@@ -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))
......@@ -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"
......@@ -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"
......
......@@ -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"
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment