Unverified Commit 2979b8cc authored by Yoav Tock's avatar Yoav Tock Committed by Artem Barger
Browse files

FAB-13705 refine Bundle.validateNew



This task addresses two issues:

1) In common/multichannel/Bundle.ValidateNew() it is possible
   to identify the system channel using:
       _, isSys := b.ConsortiumsConfig()
   this can be used to refine validateMigrationStep() such that
   it deals more accurately with the migration-state transitions
   on the system vs. standard channels.

2) In addition, prevent user from adding ConsortiumsConfig() to
   standard channels. This will protect multichannel.Registrar
   from blowing up on next initialization. Explanation:

   - Looking at the code in multichannel.Registrar, we see that
       _, ok := ledgerResources.ConsortiumsConfig()
   is used to identify the system channel. If two system channels
   are identified, the code panics.

   - Now, in Bundle.ValidateNew(), currently there is no mechanism
   to prevent a user (orderer admin) from adding a ConsortiumsConfig()
   to a standard channel. If a user does that,  multichannel.Registrar
   will blow up in the next initialization.

Change-Id: Ia7551cbd27389a9988757af0224abdc0d1bfef5b
Signed-off-by: default avatarYoav Tock <tock@il.ibm.com>
parent 5af9c275
......@@ -83,21 +83,21 @@ func (b *Bundle) ValidateNew(nb Resources) error {
if oc, ok := b.OrdererConfig(); ok {
noc, ok := nb.OrdererConfig()
if !ok {
return errors.New("Current config has orderer section, but new config does not")
return errors.New("current config has orderer section, but new config does not")
}
// Prevent consensus-type migration when capabilities Kafka2RaftMigration is disabled
if !oc.Capabilities().Kafka2RaftMigration() {
if oc.ConsensusType() != noc.ConsensusType() {
return errors.Errorf("Attempted to change consensus type from %s to %s",
return errors.Errorf("attempted to change consensus type from %s to %s",
oc.ConsensusType(), noc.ConsensusType())
}
if noc.ConsensusMigrationState() != ab.ConsensusType_MIG_STATE_NONE || noc.ConsensusMigrationContext() != 0 {
return errors.Errorf("New config has unexpected consensus-migration state or context: (%s/%d) should be (MIG_STATE_NONE/0)",
return errors.Errorf("new config has unexpected consensus-migration state or context: (%s/%d) should be (MIG_STATE_NONE/0)",
noc.ConsensusMigrationState().String(), noc.ConsensusMigrationContext())
}
} else {
if err := validateMigrationStep(oc, noc); err != nil {
if err := b.validateMigrationStep(oc, noc); err != nil {
return err
}
}
......@@ -109,7 +109,7 @@ func (b *Bundle) ValidateNew(nb Resources) error {
}
mspID := org.MSPID()
if mspID != norg.MSPID() {
return errors.Errorf("Orderer org %s attempted to change MSP ID from %s to %s", orgName, mspID, norg.MSPID())
return errors.Errorf("orderer org %s attempted to change MSP ID from %s to %s", orgName, mspID, norg.MSPID())
}
}
}
......@@ -117,7 +117,7 @@ func (b *Bundle) ValidateNew(nb Resources) error {
if ac, ok := b.ApplicationConfig(); ok {
nac, ok := nb.ApplicationConfig()
if !ok {
return errors.New("Current config has application section, but new config does not")
return errors.New("current config has application section, but new config does not")
}
for orgName, org := range ac.Organizations() {
......@@ -127,7 +127,7 @@ func (b *Bundle) ValidateNew(nb Resources) error {
}
mspID := org.MSPID()
if mspID != norg.MSPID() {
return errors.Errorf("Application org %s attempted to change MSP ID from %s to %s", orgName, mspID, norg.MSPID())
return errors.Errorf("application org %s attempted to change MSP ID from %s to %s", orgName, mspID, norg.MSPID())
}
}
}
......@@ -135,7 +135,7 @@ func (b *Bundle) ValidateNew(nb Resources) error {
if cc, ok := b.ConsortiumsConfig(); ok {
ncc, ok := nb.ConsortiumsConfig()
if !ok {
return errors.Errorf("Current config has consortiums section, but new config does not")
return errors.Errorf("current config has consortiums section, but new config does not")
}
for consortiumName, consortium := range cc.Consortiums() {
......@@ -151,10 +151,12 @@ func (b *Bundle) ValidateNew(nb Resources) error {
}
mspID := org.MSPID()
if mspID != norg.MSPID() {
return errors.Errorf("Consortium %s org %s attempted to change MSP ID from %s to %s", consortiumName, orgName, mspID, norg.MSPID())
return errors.Errorf("consortium %s org %s attempted to change MSP ID from %s to %s", consortiumName, orgName, mspID, norg.MSPID())
}
}
}
} else if _, okNew := nb.ConsortiumsConfig(); okNew {
return errors.Errorf("current config has no consortiums section, but new config does")
}
return nil
......@@ -253,96 +255,198 @@ func preValidate(config *cb.Config) error {
}
// validateMigrationStep checks the validity of the state transitions of a possible migration step.
// Since at this point we don't know whether it is a system or standard channel, we allow a wider range of options.
// The migration state machine (for both types of channels) is enforced in the chain implementation.
func validateMigrationStep(oc Orderer, noc Orderer) error {
// The system channel is identified in order to more accurately evaluate the state transitions of
// system vs. standard channels. The migration state machine (for both types of channels) is enforced
// in the chain implementation, after ordering.
//
// Consensus-type changes from Kafka to Raft in the "green" path:
// - The system channel starts the migration
// - A standard channel prepares the context, type change Kafka to Raft
// - The system channel commits the migration, type change Kafka to Raft
// Consensus-type changes from Raft to Kafka in the "abort" path:
// - The system channel starts the migration
// - A standard channel prepares the context, type change Kafka to Raft
// - The system channel aborts the migration
// - The standard channel reverts the type back, type change Raft to Kafka
//
func (b *Bundle) validateMigrationStep(oc Orderer, noc Orderer) error {
oldType := oc.ConsensusType()
oldState := oc.ConsensusMigrationState()
oldContext := oc.ConsensusMigrationContext()
newType := noc.ConsensusType()
newState := noc.ConsensusMigrationState()
newContext := noc.ConsensusMigrationContext()
_, isSys := b.ConsortiumsConfig()
logger.Debugf("Validating potential consensus-type migration, system=%t; Old: %s / %s / %d;"+
" New: %s / %s / %d; (Type/State/Context)", isSys, oldType, oldState, oldContext, newType, newState, newContext)
if isSys {
return validateMigrationStepSystem(oldType, newType, oldState, newState, newContext)
} else {
return validateMigrationStepStandard(oldType, newType, oldState, newState, oldContext, newContext)
}
}
// validateMigrationStepSystem checks the validity of the state transitions of a possible migration step
// on the system channel.
func validateMigrationStepSystem(oldType, newType string, oldState, newState ab.ConsensusType_MigrationState, newContext uint64) error {
// Check validity of new state, type, and context
unExpCtx := "Consensus-type migration, state=%s, unexpected context, actual=%d (expected=%s)"
unExpType := "Consensus-type migration, state=%s, unexpected type, actual=%s (expected=%s)"
switch newState {
case ab.ConsensusType_MIG_STATE_NONE:
if newContext != 0 {
return errors.Errorf(unExpCtx, newState, newContext, "0")
}
case ab.ConsensusType_MIG_STATE_START:
if newType != "kafka" {
return errors.Errorf(unExpType, newState, newType, "kafka")
}
if newContext != 0 {
return errors.Errorf(unExpCtx, newState, newContext, "0")
}
case ab.ConsensusType_MIG_STATE_COMMIT:
if newType != "etcdraft" {
return errors.Errorf(unExpType, newState, newType, "etcdraft")
}
if newContext <= 0 {
return errors.Errorf(unExpCtx, newState, newContext, ">0")
}
case ab.ConsensusType_MIG_STATE_ABORT:
if newType != "kafka" {
return errors.Errorf(unExpType, newState, newType, "kafka")
}
if newContext <= 0 {
return errors.Errorf(unExpCtx, newState, newContext, ">0")
}
default:
return errors.Errorf("Consensus-type migration, state=%s, not permitted on system channel", newState)
}
// The following code explicitly checks for permitted transitions; all other transitions return an error.
if oldType != newType {
// Consensus-type changes from Kafka to Raft in the "green" path:
// - The system channel starts the migration
// - A standard channel prepares the context, type change Kafka to Raft
// - The system channel commits the migration, type change Kafka to Raft
// Consensus-type changes from Raft to Kafka in the "abort" path:
// - The system channel starts the migration
// - A standard channel prepares the context, type change Kafka to Raft
// - The system channel aborts the migration
// - The standard channel reverts the type back, type change Raft to Kafka
if oldType == "kafka" && newType == "etcdraft" {
// On the system channels, this is permitted, green path commit
isSysCommit := oldState == ab.ConsensusType_MIG_STATE_START && newState == ab.ConsensusType_MIG_STATE_COMMIT
// On the standard channels, this is permitted, green path context
isStdCtx := oldState == ab.ConsensusType_MIG_STATE_NONE && newState == ab.ConsensusType_MIG_STATE_CONTEXT
if isSysCommit || isStdCtx {
logger.Debugf("Kafka-to-etcdraft migration, config update, state transition: %s to %s", oldState, newState)
} else {
return errors.Errorf("Attempted to change consensus type from %s to %s, unexpected migration state transition: %s to %s",
isSysCommit := (oldState == ab.ConsensusType_MIG_STATE_START) && (newState == ab.ConsensusType_MIG_STATE_COMMIT)
if !isSysCommit {
return errors.Errorf("Attempted to change consensus type from %s to %s, unexpected state transition: %s to %s",
oldType, newType, oldState, newState)
}
} else if oldType == "etcdraft" && newType == "kafka" {
// On the standard channels, this is permitted, abort path
if oldState == ab.ConsensusType_MIG_STATE_CONTEXT && newState == ab.ConsensusType_MIG_STATE_NONE {
logger.Debugf("Kafka-to-etcdraft migration, config update, state transition: %s to %s", oldState, newState)
} else {
return errors.Errorf("Attempted to change consensus type from %s to %s, unexpected migration state transition: %s to %s",
oldType, newType, oldState, newState)
}
return errors.Errorf("Attempted to change consensus type from %s to %s, not permitted on system channel", oldType, newType)
} else {
return errors.Errorf("Attempted to change consensus type from %s to %s, only kafka to etcdraft is supported",
oldType, newType)
return errors.Errorf("Attempted to change consensus type from %s to %s, not supported", oldType, newType)
}
logger.Debugf("Consensus-type migration, from %s to %s, state transition: %s to %s", oldType, newType, oldState, newState)
} else {
// On the system channel & standard channels, this is always permitted, not a migration
isNotMig := oldState == ab.ConsensusType_MIG_STATE_NONE && newState == ab.ConsensusType_MIG_STATE_NONE
// This is always permitted, not a migration
isNotMig := (oldState == ab.ConsensusType_MIG_STATE_NONE) && (newState == oldState)
if isNotMig {
logger.Debugf("Not Consensus-type migration, type %s, old-state = new-state = %s", oldType, oldState)
return nil
}
// Migration state may change when the type stays the same
if oldType == "kafka" {
// On the system channel, these transitions are permitted
// In the "green" path: the system channel starts migration
isSysStart := oldState == ab.ConsensusType_MIG_STATE_NONE && newState == ab.ConsensusType_MIG_STATE_START
isStart := (oldState == ab.ConsensusType_MIG_STATE_NONE) && (newState == ab.ConsensusType_MIG_STATE_START)
// In the "abort" path: the system channel aborts a migration
isSysAbort := oldState == ab.ConsensusType_MIG_STATE_START && newState == ab.ConsensusType_MIG_STATE_ABORT
isAbort := (oldState == ab.ConsensusType_MIG_STATE_START) && (newState == ab.ConsensusType_MIG_STATE_ABORT)
// In the "abort" path: the system channel reconfigures after an abort, not a migration
isSysNotMigAfterAbort := oldState == ab.ConsensusType_MIG_STATE_ABORT && newState == ab.ConsensusType_MIG_STATE_NONE
isNotMigAfterAbort := (oldState == ab.ConsensusType_MIG_STATE_ABORT) && (newState == ab.ConsensusType_MIG_STATE_NONE)
// In the "abort" path: the system channel starts a new migration attempt after an abort
isSysStartAfterAbort := oldState == ab.ConsensusType_MIG_STATE_ABORT && newState == ab.ConsensusType_MIG_STATE_START
if !(isNotMig || isSysStart || isSysAbort || isSysNotMigAfterAbort || isSysStartAfterAbort) {
isStartAfterAbort := (oldState == ab.ConsensusType_MIG_STATE_ABORT) && (newState == ab.ConsensusType_MIG_STATE_START)
if !(isNotMig || isStart || isAbort || isNotMigAfterAbort || isStartAfterAbort) {
return errors.Errorf("Consensus type %s, unexpected migration state transition: %s to %s",
oldType, oldState, newState)
} else if newState != ab.ConsensusType_MIG_STATE_NONE {
logger.Debugf("Kafka-to-etcdraft migration, config update, state transition: %s to %s", oldState, newState)
}
} else if oldType == "etcdraft" {
// On the system channel, this is permitted
// In the "green" path: the system channel reconfigures after a successful migration
isSysAfterSuccess := oldState == ab.ConsensusType_MIG_STATE_COMMIT && newState == ab.ConsensusType_MIG_STATE_NONE
// On the standard channels, this is permitted
// In the "green" path: a standard channel reconfigures after a successful migration
isStdAfterSuccess := oldState == ab.ConsensusType_MIG_STATE_CONTEXT && newState == ab.ConsensusType_MIG_STATE_NONE
if !(isNotMig || isSysAfterSuccess || isStdAfterSuccess) {
isConfAfterSuccess := (oldState == ab.ConsensusType_MIG_STATE_COMMIT) && (newState == ab.ConsensusType_MIG_STATE_NONE)
if !(isNotMig || isConfAfterSuccess) {
return errors.Errorf("Consensus type %s, unexpected migration state transition: %s to %s",
oldType, oldState.String(), newState)
}
}
logger.Debugf("Consensus-type migration, type %s, state transition: %s to %s", oldType, oldState, newState)
}
// Check for a valid range on migration context
return nil
}
// validateMigrationStepStandard checks the validity of the state transitions of a possible migration step
// on the standard channel.
func validateMigrationStepStandard(oldType, newType string, oldState, newState ab.ConsensusType_MigrationState, oldContext, newContext uint64) error {
// Check validity of new state, type, and context
unExpCtx := "Consensus-type migration, state=%s, unexpected context, actual=%d (expected=%s)"
switch newState {
case ab.ConsensusType_MIG_STATE_START, ab.ConsensusType_MIG_STATE_ABORT, ab.ConsensusType_MIG_STATE_NONE:
case ab.ConsensusType_MIG_STATE_NONE:
if newContext != 0 {
return errors.Errorf("Consensus migration state %s, unexpected migration context: %d (expected: 0)",
newState, newContext)
return errors.Errorf(unExpCtx, newState, newContext, "0")
}
case ab.ConsensusType_MIG_STATE_CONTEXT:
if newType != "etcdraft" {
unExpType := "Consensus-type migration, state=%s, unexpected type, actual=%s (expected=%s)"
return errors.Errorf(unExpType, newState, newType, "etcdraft")
}
case ab.ConsensusType_MIG_STATE_CONTEXT, ab.ConsensusType_MIG_STATE_COMMIT:
if newContext <= 0 {
return errors.Errorf("Consensus migration state %s, unexpected migration context: %d (expected >0)",
newState, newContext)
return errors.Errorf(unExpCtx, newState, newContext, ">0")
}
default:
return errors.Errorf("Consensus-type migration, state=%s, not permitted on standard channel", newState)
}
// The following code explicitly checks for permitted transitions; all other transitions return an error.
if oldType != newType {
badAttemptStr := "Attempted to change consensus type from %s to %s, unexpected state transition: %s to %s"
if oldType == "kafka" && newType == "etcdraft" {
// On the standard channels, this is permitted, green path context
isCtx := (oldState == ab.ConsensusType_MIG_STATE_NONE) && (newState == ab.ConsensusType_MIG_STATE_CONTEXT)
if !isCtx {
return errors.Errorf(badAttemptStr, oldType, newType, oldState, newState)
}
} else if oldType == "etcdraft" && newType == "kafka" {
// On the standard channels, this is permitted, abort path
isAbort := (oldState == ab.ConsensusType_MIG_STATE_CONTEXT) && (newState == ab.ConsensusType_MIG_STATE_NONE)
if !isAbort {
return errors.Errorf(badAttemptStr, oldType, newType, oldState, newState)
}
} else {
return errors.Errorf("Attempted to change consensus type from %s to %s, not supported", oldType, newType)
}
logger.Debugf("Consensus-type migration, from %s to %s, state transition: %s to %s", oldType, newType, oldState, newState)
} else {
// This is always permitted, not a migration
isNotMig := (oldState == ab.ConsensusType_MIG_STATE_NONE) && (newState == oldState)
if isNotMig {
logger.Debugf("Not Consensus-type migration, type %s, old-state = new-state = %s", oldType, oldState)
return nil
}
// Migration state may change when the type stays the same
if oldType == "etcdraft" {
// In the "green" path: a channel reconfigures after a successful migration
isConfigAfterSuccess := (oldState == ab.ConsensusType_MIG_STATE_CONTEXT) && (newState == ab.ConsensusType_MIG_STATE_NONE)
// In the "green" path: a channel may amend its CONTEXT (Metadata) as long as migration is pending, i.e.
// system channel started, not yet committed. The context must be the same.
isCtxAmend := (oldState == ab.ConsensusType_MIG_STATE_CONTEXT) && (oldState == newState) && (oldContext == newContext)
if !(isNotMig || isConfigAfterSuccess || isCtxAmend) {
return errors.Errorf("Consensus type %s, unexpected migration state transition: %s to %s", oldType, oldState, newState)
}
} else if oldType == "kafka" {
// Not a migration
if !isNotMig {
return errors.Errorf("Consensus type %s, unexpected migration state transition: %s to %s", oldType, oldState, newState)
}
}
logger.Debugf("Consensus-type migration, type %s, state transition: %s to %s", oldType, oldState, newState)
}
return nil
......
This diff is collapsed.
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