Skip to content

Commit 4dfbc9a

Browse files
authored
Improve throughput of featured app activity trigger (#2785)
Part of #2757 [ci] Signed-off-by: Simon Meier <simon@digitalasset.com>
1 parent 2becae6 commit 4dfbc9a

File tree

10 files changed

+397
-94
lines changed

10 files changed

+397
-94
lines changed

apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/EnvironmentDefinition.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@ case class EnvironmentDefinition(
274274
def withBftSequencers: EnvironmentDefinition =
275275
addConfigTransformToFront((_, config) => ConfigTransforms.withBftSequencers()(config))
276276

277+
def withEagerAppActivityMarkerConversion: EnvironmentDefinition =
278+
addConfigTransforms((_, conf) =>
279+
ConfigTransforms.updateAllSvAppConfigs_(config =>
280+
config.copy(
281+
delegatelessAutomationFeaturedAppActivityMarkerMaxAge = NonNegativeFiniteDuration.Zero
282+
)
283+
)(conf)
284+
)
285+
277286
def withAmuletPrice(price: BigDecimal): EnvironmentDefinition =
278287
addConfigTransforms((_, conf) => ConfigTransforms.setAmuletPrice(price)(conf))
279288

@@ -453,6 +462,7 @@ object EnvironmentDefinition extends CommonAppInstanceReferences {
453462
.withInitializedNodes()
454463
.withTrafficTopupsEnabled
455464
.withInitialPackageVersions
465+
.withEagerAppActivityMarkerConversion
456466
}
457467

458468
def simpleTopology4Svs(testName: String): EnvironmentDefinition = {
@@ -461,6 +471,7 @@ object EnvironmentDefinition extends CommonAppInstanceReferences {
461471
.withInitializedNodes()
462472
.withTrafficTopupsEnabled
463473
.withInitialPackageVersions
474+
.withEagerAppActivityMarkerConversion
464475
}
465476

466477
def simpleTopology1SvWithSimTime(testName: String): EnvironmentDefinition =

apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/FeaturedAppActivityMarkerIntegrationTest.scala

Lines changed: 77 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.Integration
1010
import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.{
1111
AdvanceOpenMiningRoundTrigger,
1212
ExpireIssuingMiningRoundTrigger,
13+
FeaturedAppActivityMarkerTrigger,
1314
}
1415
import org.lfdecentralizedtrust.splice.util.*
1516
import com.digitalasset.canton.config.NonNegativeFiniteDuration
1617
import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet
1718
import org.lfdecentralizedtrust.splice.codegen.java.splice.api.featuredapprightv1
18-
import org.lfdecentralizedtrust.splice.wallet.store.TransferTxLogEntry
19+
1920
import scala.jdk.CollectionConverters.*
2021

2122
@org.lfdecentralizedtrust.splice.util.scalatesttags.SpliceAmulet_0_1_9
@@ -28,7 +29,8 @@ class FeaturedAppActivityMarkerIntegrationTest
2829

2930
override def environmentDefinition: SpliceEnvironmentDefinition =
3031
EnvironmentDefinition
31-
.simpleTopology1Sv(this.getClass.getSimpleName)
32+
// Using 4Svs so that we see whether they manage to jointly complete all work
33+
.simpleTopology4Svs(this.getClass.getSimpleName)
3234
.addConfigTransforms((_, config) =>
3335
ConfigTransforms.updateAllSvAppFoundDsoConfigs_(
3436
_.copy(
@@ -44,6 +46,15 @@ class FeaturedAppActivityMarkerIntegrationTest
4446
updateAutomationConfig(ConfigurableApp.Sv)(
4547
_.withPausedTrigger[AdvanceOpenMiningRoundTrigger]
4648
.withPausedTrigger[ExpireIssuingMiningRoundTrigger]
49+
.withPausedTrigger[FeaturedAppActivityMarkerTrigger]
50+
)(config)
51+
)
52+
.addConfigTransforms((_, config) =>
53+
ConfigTransforms.updateAllSvAppConfigs_(
54+
_.copy(
55+
delegatelessAutomationFeaturedAppActivityMarkerCatchupThreshold = 10,
56+
delegatelessAutomationFeaturedAppActivityMarkerBatchSize = 2,
57+
)
4758
)(config)
4859
)
4960

@@ -58,92 +69,79 @@ class FeaturedAppActivityMarkerIntegrationTest
5869
.selfGrantFeaturedAppRight()
5970
.toInterface(featuredapprightv1.FeaturedAppRight.INTERFACE)
6071

72+
val markerMultiplier = 10
73+
6174
actAndCheck(
6275
"Create activity markers", {
63-
aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands
64-
.submitJava(
65-
Seq(alice),
66-
commands = aliceFeaturedAppRightCid
67-
.exerciseFeaturedAppRight_CreateActivityMarker(
68-
Seq(
69-
new featuredapprightv1.AppRewardBeneficiary(
70-
alice.toProtoPrimitive,
71-
BigDecimal(0.2).bigDecimal,
72-
),
73-
new featuredapprightv1.AppRewardBeneficiary(
74-
charlie.toProtoPrimitive,
75-
BigDecimal(0.8).bigDecimal,
76-
),
77-
).asJava
78-
)
79-
.commands
80-
.asScala
81-
.toSeq,
82-
)
83-
84-
bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands
85-
.submitJava(
86-
Seq(bob),
87-
commands = bobFeaturedAppRightCid
88-
.exerciseFeaturedAppRight_CreateActivityMarker(
89-
Seq(
90-
new featuredapprightv1.AppRewardBeneficiary(
91-
bob.toProtoPrimitive,
92-
BigDecimal(1.0).bigDecimal,
93-
)
94-
).asJava
95-
)
96-
.commands
97-
.asScala
98-
.toSeq,
99-
)
76+
for (i <- 1 to markerMultiplier) {
77+
aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands
78+
.submitJava(
79+
Seq(alice),
80+
commands = aliceFeaturedAppRightCid
81+
.exerciseFeaturedAppRight_CreateActivityMarker(
82+
Seq(
83+
new featuredapprightv1.AppRewardBeneficiary(
84+
alice.toProtoPrimitive,
85+
BigDecimal(0.2).bigDecimal,
86+
),
87+
new featuredapprightv1.AppRewardBeneficiary(
88+
charlie.toProtoPrimitive,
89+
BigDecimal(0.8).bigDecimal,
90+
),
91+
).asJava
92+
)
93+
.commands
94+
.asScala
95+
.toSeq,
96+
)
97+
bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands
98+
.submitJava(
99+
Seq(bob),
100+
commands = bobFeaturedAppRightCid
101+
.exerciseFeaturedAppRight_CreateActivityMarker(
102+
Seq(
103+
new featuredapprightv1.AppRewardBeneficiary(
104+
bob.toProtoPrimitive,
105+
BigDecimal(1.0).bigDecimal,
106+
)
107+
).asJava
108+
)
109+
.commands
110+
.asScala
111+
.toSeq,
112+
)
113+
}
114+
// unpause all activity marker triggers here, so they can start to get to work
115+
env.svs.local.foreach(
116+
_.dsoDelegateBasedAutomation.trigger[FeaturedAppActivityMarkerTrigger].resume()
117+
)
100118
},
101119
)(
102120
"Activity markers are converted to reward coupons",
103121
_ => {
104-
aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
105-
amulet.AppRewardCoupon.COMPANION
106-
)(alice, c => c.data.provider == alice.toProtoPrimitive) should have size 2
107-
bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
108-
amulet.AppRewardCoupon.COMPANION
109-
)(bob, c => c.data.provider == bob.toProtoPrimitive) should have size 1
110-
},
111-
)
112-
113-
// Advance three times so the round the coupons are assigned to is issuing
114-
actAndCheck(
115-
"Advance until reward coupon round is issuing", {
116-
advanceRoundsByOneTickViaAutomation()
117-
advanceRoundsByOneTickViaAutomation()
118-
advanceRoundsByOneTickViaAutomation()
119-
},
120-
)(
121-
"Rewards are minted",
122-
_ => {
123-
aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
124-
amulet.AppRewardCoupon.COMPANION
125-
)(alice, c => c.data.provider == alice.toProtoPrimitive) shouldBe empty
126-
bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
127-
amulet.AppRewardCoupon.COMPANION
128-
)(bob, c => c.data.provider == bob.toProtoPrimitive) shouldBe empty
129-
130-
// Check that tx logs work as expected, the exact amounts are just based on testing, the important part is alice sees only the minting for the reward she is a beneficiary on
131-
// and not the one for charlie even though she is the provider and the maounts of alice and charlie add up to bob.
122+
sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
123+
amulet.FeaturedAppActivityMarker.COMPANION
124+
)(dsoParty, _ => true) should have size 0
132125
inside(
133-
aliceWalletClient.listTransactions(beginAfterId = None, pageSize = 1000).loneElement
134-
) { case transfer: TransferTxLogEntry =>
135-
transfer.appRewardsUsed should beAround(9.0)
126+
aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs
127+
.filterJava(
128+
amulet.AppRewardCoupon.COMPANION
129+
)(alice, c => c.data.provider == alice.toProtoPrimitive)
130+
) { aliceCoupons =>
131+
// The actual number of coupons is non-deterministic due to the random sampling and
132+
// the batches only creating one coupon per beneficiary. There are at least two, as there are two beneficiaries.
133+
aliceCoupons.size should be >= 2
134+
val totalWeight: BigDecimal = aliceCoupons.map(co => BigDecimal(co.data.amount)).sum
135+
totalWeight shouldBe BigDecimal(markerMultiplier)
136136
}
137-
138137
inside(
139-
charlieWalletClient.listTransactions(beginAfterId = None, pageSize = 1000).loneElement
140-
) { case transfer: TransferTxLogEntry =>
141-
transfer.appRewardsUsed should beAround(38.0)
142-
}
143-
144-
inside(bobWalletClient.listTransactions(beginAfterId = None, pageSize = 1000).loneElement) {
145-
case transfer: TransferTxLogEntry =>
146-
transfer.appRewardsUsed should beAround(47.0)
138+
bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.acs.filterJava(
139+
amulet.AppRewardCoupon.COMPANION
140+
)(bob, c => c.data.provider == bob.toProtoPrimitive)
141+
) { bobCoupons =>
142+
bobCoupons.size should be >= 1
143+
val totalWeight: BigDecimal = bobCoupons.map(co => BigDecimal(co.data.amount)).sum
144+
totalWeight shouldBe BigDecimal(markerMultiplier)
147145
}
148146
},
149147
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Int32 hash that is stable across Postgres versions.
2+
-- Note: we do not need cryptographic security, and thus md5 is fine.
3+
CREATE FUNCTION stable_int32_hash(input text)
4+
RETURNS int AS $$
5+
SELECT ('x' || substr(md5(input), 1, 8))::bit(32)::int;
6+
$$ LANGUAGE sql IMMUTABLE;
7+
8+
9+
-- An expression index to make it easy to randomly select a batch of N featured app activity markers to convert
10+
-- by picking a random int32 and selecting the next N app activity markers.
11+
create index dso_acs_contract_id_hash_idx on dso_acs_store (store_id, migration_id, stable_int32_hash(contract_id))
12+
where template_id_qualified_name = 'Splice.Amulet:FeaturedAppActivityMarker';
13+
14+

apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/StoreTest.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,26 @@ abstract class StoreTest extends AsyncWordSpec with BaseTest {
364364
),
365365
)
366366

367+
protected def appActivityMarker(
368+
provider: PartyId,
369+
weight: Numeric.Numeric = numeric(1.0),
370+
beneficiary: Option[PartyId] = None,
371+
contractId: String = nextCid(),
372+
): Contract[
373+
amuletCodegen.FeaturedAppActivityMarker.ContractId,
374+
amuletCodegen.FeaturedAppActivityMarker,
375+
] =
376+
contract(
377+
identifier = amuletCodegen.FeaturedAppActivityMarker.TEMPLATE_ID_WITH_PACKAGE_ID,
378+
contractId = new amuletCodegen.FeaturedAppActivityMarker.ContractId(contractId),
379+
payload = new amuletCodegen.FeaturedAppActivityMarker(
380+
dsoParty.toProtoPrimitive,
381+
provider.toProtoPrimitive,
382+
beneficiary.getOrElse(provider).toProtoPrimitive,
383+
weight,
384+
),
385+
)
386+
367387
protected def numeric(value: BigDecimal, scale: Int = 10) = {
368388
Numeric.assertFromBigDecimal(Numeric.Scale.assertFromInt(scale), value)
369389
}

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class DsoDelegateBasedAutomationService(
9797
new FeaturedAppActivityMarkerTrigger(
9898
triggerContext,
9999
svTaskContext,
100+
config,
100101
)
101102
)
102103

0 commit comments

Comments
 (0)