diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala index 47ec306f39..24a684b742 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala @@ -1,10 +1,15 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.config.CantonRequireTypes.InstanceName -import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.framework.data.topology.SequencingParameters +import com.digitalasset.canton.config.PositiveFiniteDuration +import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.framework.data.topology.{ + BlacklistLeaderSelectionPolicyConfig, + SequencingParameters, +} import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTest +import org.lfdecentralizedtrust.splice.sv.config.BftSequencingParameters import org.lfdecentralizedtrust.splice.util.StandaloneCanton class SvReconcileBftSequencingParametersIntegrationTest @@ -27,13 +32,26 @@ class SvReconcileBftSequencingParametersIntegrationTest (InstanceName.tryCreate("sv1Local") -> c.svApps(InstanceName.tryCreate("sv1")) .copy( - cantonBftSequencingParameters = None + cantonBftSequencingParameters = Some( + BftSequencingParameters( + pbftViewChangeTimeout = PositiveFiniteDuration.ofSeconds(5), + segmentLength = SequencingParameters.DefaultSegmentLength.length, + blacklistLeaderSelectionPolicyConfig = + SequencingParameters.DefaultLeaderSelectionPolicyConfig.copy( + howLongToBlacklist = + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential( + initialValue = 1L, + maximumEpochBlacklisted = Some(250L), + ) + ), + ) + ) )) ), ) .withManualStart - "SV automation can set and unset bft sequencing parameters" in { implicit env => + "SV automation can modify bft sequencing parameters" in { implicit env => withCantonSvNodes( ( Some(sv1Backend), @@ -56,12 +74,26 @@ class SvReconcileBftSequencingParametersIntegrationTest .value bftParameters.pbftViewChangeTimeout shouldBe com.digitalasset.canton.time.PositiveFiniteDuration .tryOfSeconds(5) + bftParameters.blacklistLeaderSelectionPolicyConfig.howLongToBlacklist shouldBe a[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear + ] sv1Backend.stop() - actAndCheck("Restart with sequencing parameters unset", sv1LocalBackend.startSync())( + actAndCheck( + "Restart with modified bft sequencing parameters unset", + sv1LocalBackend.startSync(), + )( "sequencing parameters are unset", _ => { - sv1LocalBackend.participantClient.topology.sequencing_parameters - .list(decentralizedSynchronizerId) shouldBe empty + val parameters = sv1Backend.participantClient.topology.sequencing_parameters + .list(decentralizedSynchronizerId) + .loneElement + val bytes = parameters.item.payload.value + val bftParameters = SequencingParameters + .fromByteString(sv1Backend.config.localSynchronizerNodes.current.protocolVersion, bytes) + .value + bftParameters.blacklistLeaderSelectionPolicyConfig.howLongToBlacklist shouldBe a[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential + ] }, ) sv1LocalBackend.stop() diff --git a/canton/community/app-base/src/main/scala/com/digitalasset/canton/config/CantonConfig.scala b/canton/community/app-base/src/main/scala/com/digitalasset/canton/config/CantonConfig.scala index 1f9490006b..7bba67d45d 100644 --- a/canton/community/app-base/src/main/scala/com/digitalasset/canton/config/CantonConfig.scala +++ b/canton/community/app-base/src/main/scala/com/digitalasset/canton/config/CantonConfig.scala @@ -1226,6 +1226,16 @@ object CantonConfig { lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistLinearConfigReader : ConfigReader[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear] = deriveReader[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear] + lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistLinearWithParametersConfigReader + : ConfigReader[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.LinearWithParameters + ] = + deriveReader[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.LinearWithParameters] + lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistExponentialConfigReader + : ConfigReader[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential + ] = + deriveReader[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential] lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistNoBlacklistingConfigReader : ConfigReader[ BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.NoBlacklisting.type @@ -2046,6 +2056,16 @@ object CantonConfig { lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistLinearConfigWriter : ConfigWriter[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear] = deriveWriter[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear] + lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistLinearWithParametersConfigWriter + : ConfigWriter[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.LinearWithParameters + ] = + deriveWriter[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.LinearWithParameters] + lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistExponentialConfigWriter + : ConfigWriter[ + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential + ] = + deriveWriter[BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential] lazy implicit val bftBlockOrdererLeaderSelectionPolicyHowLongToBlacklistNoBlacklistingConfigWriter : ConfigWriter[ BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.NoBlacklisting.type diff --git a/canton/community/synchronizer/src/main/protobuf/com/digitalasset/canton/synchronizer/sequencing/sequencer/bftordering/v31/bft_ordering_sequencing_parameters.proto b/canton/community/synchronizer/src/main/protobuf/com/digitalasset/canton/synchronizer/sequencing/sequencer/bftordering/v31/bft_ordering_sequencing_parameters.proto index dfc907164d..d5baf2f6ca 100644 --- a/canton/community/synchronizer/src/main/protobuf/com/digitalasset/canton/synchronizer/sequencing/sequencer/bftordering/v31/bft_ordering_sequencing_parameters.proto +++ b/canton/community/synchronizer/src/main/protobuf/com/digitalasset/canton/synchronizer/sequencing/sequencer/bftordering/v31/bft_ordering_sequencing_parameters.proto @@ -20,6 +20,8 @@ message BlacklistLeaderSelectionPolicy { oneof how_long_to_blacklist { HowLongLinear how_long_linear = 1; HowLongNoBlacklisting how_long_no_blacklisting = 2; + HowLongLinearWithParameters how_long_linear_with_parameters = 5; + HowLongExponential how_long_exponential = 6; } oneof how_many_can_we_blacklist { HowManyNumFaultsTolerated how_many_num_faults_tolerated = 3; @@ -31,6 +33,15 @@ message HowLongLinear { optional int64 maximum_epoch_length_blacklisted = 1; } message HowLongNoBlacklisting {} +message HowLongLinearWithParameters { + int64 slope = 1; + int64 initial_value = 2; + optional int64 maximum_epoch_length_blacklisted = 3; +} +message HowLongExponential { + int64 initial_value = 1; + optional int64 maximum_epoch_length_blacklisted = 2; +} message HowManyNumFaultsTolerated {} message HowManyNoBlacklisting {} diff --git a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionInitializer.scala b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionInitializer.scala index a4c79885ee..6d7890424f 100644 --- a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionInitializer.scala +++ b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionInitializer.scala @@ -51,7 +51,7 @@ class BlacklistLeaderSelectionInitializer[E <: Env[E]]( state: BlacklistLeaderSelectionPolicyState, orderingTopology: OrderingTopology, ): Seq[BftNodeId] = - BlacklistLeaderSelectionPolicyStateWithTopology(state, orderingTopology) + BlacklistLeaderSelectionPolicyStateWithTopology(state, orderingTopology, protocolVersion) .computeLeaders() def blacklistedNodesFromState( @@ -61,6 +61,7 @@ class BlacklistLeaderSelectionInitializer[E <: Env[E]]( BlacklistLeaderSelectionPolicyStateWithTopology( state, orderingTopology, + protocolVersion, ).computeBlacklistedNodes() def leaderSelectionPolicy( @@ -69,6 +70,7 @@ class BlacklistLeaderSelectionInitializer[E <: Env[E]]( ): LeaderSelectionPolicy[E] = BlacklistLeaderSelectionPolicy.create( blacklistLeaderSelectionPolicyState, orderingTopology, + protocolVersion, store, metrics, loggerFactory, diff --git a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicy.scala b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicy.scala index 66b51bbd0e..579b87c13f 100644 --- a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicy.scala +++ b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicy.scala @@ -15,6 +15,7 @@ import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.framewor FutureContext, } import com.digitalasset.canton.tracing.TraceContext +import com.digitalasset.canton.version.ProtocolVersion import scala.collection.mutable @@ -22,6 +23,7 @@ import scala.collection.mutable class BlacklistLeaderSelectionPolicy[E <: Env[E]]( initialState: BlacklistLeaderSelectionPolicyState, initialOrderingTopology: OrderingTopology, + protocolVersion: ProtocolVersion, store: OutputMetadataStore[E], metrics: BftOrderingMetrics, override val loggerFactory: NamedLoggerFactory, @@ -30,7 +32,11 @@ class BlacklistLeaderSelectionPolicy[E <: Env[E]]( with NamedLogging { private var state = - BlacklistLeaderSelectionPolicyStateWithTopology(initialState, initialOrderingTopology) + BlacklistLeaderSelectionPolicyStateWithTopology( + initialState, + initialOrderingTopology, + protocolVersion, + ) private var blockToLeader: Map[BlockNumber, BftNodeId] = state.computeBlockToLeader() @@ -161,6 +167,7 @@ object BlacklistLeaderSelectionPolicy { def create[E <: Env[E]]( state: BlacklistLeaderSelectionPolicyState, orderingTopology: OrderingTopology, + protocolVersion: ProtocolVersion, store: OutputMetadataStore[E], metrics: BftOrderingMetrics, loggerFactory: NamedLoggerFactory, @@ -168,6 +175,7 @@ object BlacklistLeaderSelectionPolicy { new BlacklistLeaderSelectionPolicy( state, orderingTopology, + protocolVersion, store, metrics, loggerFactory, diff --git a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyState.scala b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyState.scala index ebe7469650..98cef2bf37 100644 --- a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyState.scala +++ b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyState.scala @@ -13,6 +13,7 @@ import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.mod import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.framework.data.BftOrderingIdentifiers.{ BftNodeId, BlockNumber, + EpochLength, EpochNumber, } import com.digitalasset.canton.synchronizer.sequencer.block.bftordering.framework.data.topology.OrderingTopology @@ -30,6 +31,7 @@ import scala.collection.immutable.SortedSet final case class BlacklistLeaderSelectionPolicyStateWithTopology( state: BlacklistLeaderSelectionPolicyState, topology: OrderingTopology, + protocolVersion: ProtocolVersion, ) { def epochNumber: EpochNumber = state.epochNumber def startBlock: BlockNumber = state.startBlock @@ -48,12 +50,9 @@ final case class BlacklistLeaderSelectionPolicyStateWithTopology( ): BlacklistLeaderSelectionPolicyStateWithTopology = { val newBlacklist = updateBlacklist(newTopology, blockToLeader, nodesToPunish) BlacklistLeaderSelectionPolicyStateWithTopology( - BlacklistLeaderSelectionPolicyState.create( - EpochNumber(epochNumber + 1), - BlockNumber(startBlock + topology.epochLength), - newBlacklist, - )(state.representativeProtocolVersion.representative), + state.update(topology.epochLength, newBlacklist, protocolVersion), newTopology, + protocolVersion, ) } private def updateBlacklist( @@ -124,6 +123,17 @@ final case class BlacklistLeaderSelectionPolicyState( blacklist.view.mapValues(_.toProto30).toMap, ) + def update( + epochLength: EpochLength, + newBlacklist: Blacklist, + protocolVersion: ProtocolVersion, + ): BlacklistLeaderSelectionPolicyState = + BlacklistLeaderSelectionPolicyState.create( + EpochNumber(epochNumber + 1), + BlockNumber(startBlock + epochLength), + newBlacklist, + )(protocolVersion) + override protected val companionObj: BlacklistLeaderSelectionPolicyState.type = BlacklistLeaderSelectionPolicyState } diff --git a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/framework/data/topology/BlacklistLeaderSelectionPolicyConfig.scala b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/framework/data/topology/BlacklistLeaderSelectionPolicyConfig.scala index 7d7e7f8638..5e7b0aef3d 100644 --- a/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/framework/data/topology/BlacklistLeaderSelectionPolicyConfig.scala +++ b/canton/community/synchronizer/src/main/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/framework/data/topology/BlacklistLeaderSelectionPolicyConfig.scala @@ -58,6 +58,23 @@ object BlacklistLeaderSelectionPolicyConfig { ParsingResult.pure(HowLongToBlacklist.Linear(value.maximumEpochLengthBlacklisted)) case v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist.HowLongNoBlacklisting(value) => ParsingResult.pure(HowLongToBlacklist.NoBlacklisting) + case v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist + .HowLongLinearWithParameters(value) => + ParsingResult.pure( + HowLongToBlacklist.LinearWithParameters( + slope = value.slope, + initialValue = value.initialValue, + maximumEpochBlacklisted = value.maximumEpochLengthBlacklisted, + ) + ) + case v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist + .HowLongExponential(value) => + ParsingResult.pure( + HowLongToBlacklist.Exponential( + initialValue = value.initialValue, + maximumEpochBlacklisted = value.maximumEpochLengthBlacklisted, + ) + ) } howManyCanWeBlacklist <- proto.howManyCanWeBlacklist match { case v31.BlacklistLeaderSelectionPolicy.HowManyCanWeBlacklist.Empty => @@ -104,6 +121,78 @@ object BlacklistLeaderSelectionPolicyConfig { maximumEpochBlacklisted.getOrElse(epochsLeftUntilNextTrial).min(epochsLeftUntilNextTrial) } + // X |-> min(slope*X+initialValue, maximumEpochBlacklisted) + final case class LinearWithParameters( + maximumEpochBlacklisted: Option[Long], + slope: Long, + initialValue: Long, + ) extends HowLongToBlacklist { + override protected def pretty: Pretty[LinearWithParameters] = prettyOfClass( + param("maximumEpochBlacklisted", _.maximumEpochBlacklisted), + param("slope", _.slope), + param("initialValue", _.initialValue), + ) + + override def punishNodeThatFailed(failedEpochSoFar: Long): BlacklistStatus = + BlacklistStatus.Blacklisted.create( + failedAttemptsBefore = failedEpochSoFar, + epochsLeftUntilNewTrial = updateLeftUntilNextTrial( + slope * failedEpochSoFar + initialValue + ), + ) + + override def updateLeftUntilNextTrial(epochsLeftUntilNextTrial: Long): Long = + maximumEpochBlacklisted.getOrElse(epochsLeftUntilNextTrial).min(epochsLeftUntilNextTrial) + + override def toProto: v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist = + v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist.HowLongLinearWithParameters( + v31.HowLongLinearWithParameters( + slope = slope, + initialValue = initialValue, + maximumEpochLengthBlacklisted = maximumEpochBlacklisted, + ) + ) + } + + final case class Exponential( + maximumEpochBlacklisted: Option[Long], + initialValue: Long, + ) extends HowLongToBlacklist { + override protected def pretty: Pretty[Exponential] = prettyOfClass( + param("maximumEpochBlacklisted", _.maximumEpochBlacklisted), + param("initialValue", _.initialValue), + ) + + override def punishNodeThatFailed(failedEpochSoFar: Long): BlacklistStatus = + BlacklistStatus.Blacklisted.create( + failedAttemptsBefore = failedEpochSoFar, + epochsLeftUntilNewTrial = updateLeftUntilNextTrial( + fixOverflow( + pow(failedEpochSoFar) + initialValue + ) + ), + ) + + private def fixOverflow(x: Long): Long = + if (x < 0) Long.MaxValue else x + + private def pow(x: Long): Long = { + val temp = scala.math.pow(2, x.toDouble) + if (temp < 0.0 || temp > Long.MaxValue.toDouble) Long.MaxValue else temp.toLong + } + + override def updateLeftUntilNextTrial(epochsLeftUntilNextTrial: Long): Long = + maximumEpochBlacklisted.getOrElse(epochsLeftUntilNextTrial).min(epochsLeftUntilNextTrial) + + override def toProto: v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist = + v31.BlacklistLeaderSelectionPolicy.HowLongToBlacklist.HowLongExponential( + v31.HowLongExponential( + initialValue = initialValue, + maximumEpochLengthBlacklisted = maximumEpochBlacklisted, + ) + ) + } + case object NoBlacklisting extends HowLongToBlacklist { override final def pretty: Pretty[this.type] = prettyOfObject[this.type] override def punishNodeThatFailed(failedEpochSoFar: Long): BlacklistStatus = diff --git a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/bindings/canton/topology/CantonOrderingTopologyProviderTest.scala b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/bindings/canton/topology/CantonOrderingTopologyProviderTest.scala index 3b36dd54ab..56f3880260 100644 --- a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/bindings/canton/topology/CantonOrderingTopologyProviderTest.scala +++ b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/bindings/canton/topology/CantonOrderingTopologyProviderTest.scala @@ -85,6 +85,10 @@ class CantonOrderingTopologyProviderTest case HowLongToBlacklist.Linear(_) => HowLongToBlacklist.NoBlacklisting case HowLongToBlacklist.NoBlacklisting => HowLongToBlacklist.Linear(Some(1L)) + case _: HowLongToBlacklist.LinearWithParameters => + HowLongToBlacklist.NoBlacklisting + case _: HowLongToBlacklist.Exponential => + HowLongToBlacklist.NoBlacklisting } ) Table[Option[Long], Option[Long], SegmentLength, Option[ diff --git a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyStateTest.scala b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyStateTest.scala index aea5f249e6..45fa2eb4e3 100644 --- a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyStateTest.scala +++ b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyStateTest.scala @@ -82,7 +82,11 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest "BlacklistLeaderSelectionPolicyState" should { "a clean node" should { "stay clean if not punished" in { - BlacklistLeaderSelectionPolicyStateWithTopology(initState(), orderingTopology) + BlacklistLeaderSelectionPolicyStateWithTopology( + initState(), + orderingTopology, + testedProtocolVersion, + ) .update( orderingTopology, blockToLeaderAll, @@ -92,7 +96,11 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest } "be blacklisted if punished" in { - BlacklistLeaderSelectionPolicyStateWithTopology(initState(), orderingTopology) + BlacklistLeaderSelectionPolicyStateWithTopology( + initState(), + orderingTopology, + testedProtocolVersion, + ) .update( orderingTopology, blockToLeaderAll, @@ -109,6 +117,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest n0 -> BlacklistStatus.Blacklisted(1, 2) ), orderingTopology, + testedProtocolVersion, ).update( orderingTopology, blockToLeaderAllWithoutN0, @@ -122,6 +131,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest n0 -> BlacklistStatus.Blacklisted(1, 1) ), orderingTopology, + testedProtocolVersion, ).update( orderingTopology, blockToLeaderAllWithoutN0, @@ -137,6 +147,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest n0 -> BlacklistStatus.OnTrial(1) ), orderingTopology, + testedProtocolVersion, ).update( orderingTopology, blockToLeaderAll, @@ -150,6 +161,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest n0 -> BlacklistStatus.OnTrial(1) ), orderingTopology, + testedProtocolVersion, ).update( orderingTopology, blockToLeaderAll, @@ -163,6 +175,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest n0 -> BlacklistStatus.OnTrial(1) ), orderingTopology, + testedProtocolVersion, ).update( orderingTopology, blockToLeaderAllWithoutN0, @@ -175,6 +188,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest BlacklistLeaderSelectionPolicyStateWithTopology( initState(n1 -> BlacklistStatus.OnTrial(1), n2 -> BlacklistStatus.Blacklisted(1, 1)), orderingTopology, + testedProtocolVersion, ).computeLeaders() shouldBe Seq(n0, n1, n3) } @@ -182,6 +196,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest BlacklistLeaderSelectionPolicyStateWithTopology( initState(n1 -> BlacklistStatus.Blacklisted(2, 2), n2 -> BlacklistStatus.Blacklisted(1, 1)), orderingTopology, + testedProtocolVersion, ) .computeLeaders() shouldBe Seq(n0, n2, n3) } @@ -194,6 +209,7 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest BlacklistLeaderSelectionPolicyConfig.HowManyCanWeBlacklist.NoBlacklisting ) ), + testedProtocolVersion, ).computeLeaders() shouldBe Seq(n0, n1, n2, n3) } @@ -201,65 +217,87 @@ class BlacklistLeaderSelectionPolicyStateTest extends AnyWordSpec with BaseTest "not blacklist further than cap" in { val limit = 10L val failedAttempts = 100L - val topology = makeOrderingTopology( - makeConfig(howLongToBlacklist = - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear( - Some(limit) - ) - ) - ) - BlacklistLeaderSelectionPolicyStateWithTopology( - initState(n1 -> BlacklistStatus.OnTrial(failedAttempts)), - topology, - ).update( - topology, - blockToLeaderAllWithoutN0, - Set(n1), - ).state shouldBe stateNextEpoch( - n1 -> BlacklistStatus.Blacklisted(failedAttempts + 1, limit) + Table( + "policy", + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear(Some(limit)), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist + .LinearWithParameters(Some(limit), 10, 10), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist + .Exponential(Some(limit), 10), ) + .forEvery { policy => + val topology = makeOrderingTopology(makeConfig(howLongToBlacklist = policy)) + BlacklistLeaderSelectionPolicyStateWithTopology( + initState(n1 -> BlacklistStatus.OnTrial(failedAttempts)), + topology, + testedProtocolVersion, + ).update( + topology, + blockToLeaderAllWithoutN0, + Set(n1), + ).state shouldBe stateNextEpoch( + n1 -> BlacklistStatus.Blacklisted(failedAttempts + 1, limit) + ) + } } "don't apply limit if you are below" in { val limit = 100L - val failedAttempts = 10L - val topology = makeOrderingTopology( - makeConfig(howLongToBlacklist = - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear( - Some(limit) - ) + val failedAttempts = 5L + Table( + ("policy", "next value"), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear( + Some(limit) + ) -> (failedAttempts + 1), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.LinearWithParameters( + Some(limit), + 5, + 10, + ) -> (5 * (failedAttempts + 1) + 10), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential( + Some(limit), + 5, + ) -> 69L, // 2 ^{5+1} + 5 + ).forEvery { case (policy, nextHowManyLeft) => + val topology = makeOrderingTopology(makeConfig(howLongToBlacklist = policy)) + BlacklistLeaderSelectionPolicyStateWithTopology( + initState(n1 -> BlacklistStatus.OnTrial(failedAttempts)), + topology, + testedProtocolVersion, + ).update( + topology, + blockToLeaderAllWithoutN0, + Set(n1), + ).state shouldBe stateNextEpoch( + n1 -> BlacklistStatus.Blacklisted(failedAttempts + 1, nextHowManyLeft) ) - ) - BlacklistLeaderSelectionPolicyStateWithTopology( - initState(n1 -> BlacklistStatus.OnTrial(failedAttempts)), - topology, - ).update( - topology, - blockToLeaderAllWithoutN0, - Set(n1), - ).state shouldBe stateNextEpoch( - n1 -> BlacklistStatus.Blacklisted(failedAttempts + 1, failedAttempts + 1) - ) + } } "update if config change" in { val oldValue = 100L val newLimit = 10L - BlacklistLeaderSelectionPolicyStateWithTopology( - initState(n0 -> BlacklistStatus.Blacklisted(oldValue, oldValue)), - orderingTopology, - ).update( - makeOrderingTopology( - makeConfig(howLongToBlacklist = - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist - .Linear(Some(newLimit)) - ) - ), - blockToLeaderAllWithoutN0, - Set.empty, - ).state shouldBe stateNextEpoch( - n0 -> BlacklistStatus.Blacklisted(oldValue, newLimit - 1) - ) + Table( + "policy", + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist + .Linear(Some(newLimit)), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist + .LinearWithParameters(Some(newLimit), 0L, 0L), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist + .Exponential(Some(newLimit), 0L), + ).forEvery { policy => + BlacklistLeaderSelectionPolicyStateWithTopology( + initState(n0 -> BlacklistStatus.Blacklisted(oldValue, oldValue)), + orderingTopology, + testedProtocolVersion, + ).update( + makeOrderingTopology(makeConfig(howLongToBlacklist = policy)), + blockToLeaderAllWithoutN0, + Set.empty, + ).state shouldBe stateNextEpoch( + n0 -> BlacklistStatus.Blacklisted(oldValue, newLimit - 1) + ) + } } } } diff --git a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyTest.scala b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyTest.scala index 80b843cdfa..a11a66ae85 100644 --- a/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyTest.scala +++ b/canton/community/synchronizer/src/test/scala/com/digitalasset/canton/synchronizer/sequencer/block/bftordering/core/modules/output/leaders/BlacklistLeaderSelectionPolicyTest.scala @@ -48,6 +48,7 @@ class BlacklistLeaderSelectionPolicyTest extends AnyWordSpec with BaseTest { BlacklistLeaderSelectionPolicy.create( state, orderingTopology, + testedProtocolVersion, store, metrics, loggerFactory, @@ -67,6 +68,7 @@ class BlacklistLeaderSelectionPolicyTest extends AnyWordSpec with BaseTest { BlacklistLeaderSelectionPolicy.create( state, orderingTopology, + testedProtocolVersion, store, metrics, loggerFactory, diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 038a77e612..7dc8dabe41 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.9", - "oss_sha256": "sha256:0b3ks8b3wfm7fbc8wqaiixylpi2mb73zfwmlaksybxwnyblr9zav", - "canton_base_image_sha256": "sha256:7ea01437e4a135aa2d5dcbd27f21e6b3e6ce11334c805f17924eb7f5f089ac52", - "canton_participant_image_sha256": "sha256:2e3be4f8fb62f2f07b1875b64aa16f2ac8c39da9aa087a6e4a05943537e76bcb", - "canton_mediator_image_sha256": "sha256:e9312f2927bfb9f99c328b16def3e1b98bb42897689c4c1ff861a648c5338817", - "canton_sequencer_image_sha256": "sha256:4cc76bc98c30ce9e764f1d37aa628f289cfa4a162b783b1c487b9cfa3bbef801" + "version": "3.5.10-snapshot.20260720.19098.0.vd9fd9641", + "oss_sha256": "sha256:1wjy9zns94vsib3hd8q2v93vb65nd3csrmx2aqmr2ll9h6fi4ffa", + "canton_base_image_sha256": "sha256:28bdcb7b32d81c7d3bf5f3bc26659426286e79c84dfc080b0817cdaa0de79e09", + "canton_participant_image_sha256": "sha256:df73ec299d33f6343a97b666f64c122bb2f9859216fe87d578c5bd09efce880c", + "canton_mediator_image_sha256": "sha256:e6858b2d0ae03ae80b67628d6b49dd998892660625509c1cc45b5f75092736f3", + "canton_sequencer_image_sha256": "sha256:4d4841d26936bb7b4db3b310a894e01f8027f715b038e69c67f022bff69e4ad9" }