From 3d9fd683e3867f1bfdde505da73efe0bd5c340f5 Mon Sep 17 00:00:00 2001 From: georgeee Date: Thu, 21 Sep 2023 16:52:51 +0200 Subject: [PATCH] Add unit test for generator --- src/app/itn_orchestrator/src/generate.go | 42 ++- src/app/itn_orchestrator/src/generate_test.go | 254 ++++++++++++++++++ .../itn_orchestrator/src/generator/main.go | 7 +- src/app/itn_orchestrator/src/payments.go | 14 +- src/app/itn_orchestrator/src/zkapp.go | 12 +- 5 files changed, 294 insertions(+), 35 deletions(-) create mode 100644 src/app/itn_orchestrator/src/generate_test.go diff --git a/src/app/itn_orchestrator/src/generate.go b/src/app/itn_orchestrator/src/generate.go index e4d5c2ff9e22..ed4eb6da57c6 100644 --- a/src/app/itn_orchestrator/src/generate.go +++ b/src/app/itn_orchestrator/src/generate.go @@ -41,8 +41,9 @@ func (cmd *GeneratedCommand) Comment() string { } type GeneratedRound struct { - Commands []GeneratedCommand - FundCommands []FundParams + Commands []GeneratedCommand + PaymentFundCommand *FundParams + ZkappFundCommand *FundParams } func withComment(comment string, cmd GeneratedCommand) GeneratedCommand { @@ -381,32 +382,27 @@ func (p *GenParams) Generate(round int) GeneratedRound { cmds = append(cmds, withComment(comment3, waitMin(p.LargePauseMin))) } } - fundCmds := []FundParams{} + res := GeneratedRound{Commands: cmds} if !onlyPayments { _, _, _, initBalance := ZkappBalanceRequirements(zkappTps, zkappParams) zkappKeysNum, zkappAmount := ZkappKeygenRequirements(initBalance, zkappParams) - fundCmds = append(fundCmds, - FundParams{ - PasswordEnv: p.PasswordEnv, - Prefix: zkappsKeysDir + "/key", - Amount: zkappAmount, - Fee: p.FundFee, - Num: zkappKeysNum, - }) + res.ZkappFundCommand = &FundParams{ + PasswordEnv: p.PasswordEnv, + Prefix: zkappsKeysDir + "/key", + Amount: zkappAmount, + Fee: p.FundFee, + Num: zkappKeysNum, + } } if !onlyZkapps { paymentKeysNum, paymentAmount := PaymentKeygenRequirements(p.Gap, paymentParams) - fundCmds = append(fundCmds, - FundParams{ - PasswordEnv: p.PasswordEnv, - Prefix: paymentsKeysDir + "/key", - Amount: paymentAmount, - Fee: p.FundFee, - Num: paymentKeysNum, - }) - } - return GeneratedRound{ - Commands: cmds, - FundCommands: fundCmds, + res.PaymentFundCommand = &FundParams{ + PasswordEnv: p.PasswordEnv, + Prefix: paymentsKeysDir + "/key", + Amount: paymentAmount, + Fee: p.FundFee, + Num: paymentKeysNum, + } } + return res } diff --git a/src/app/itn_orchestrator/src/generate_test.go b/src/app/itn_orchestrator/src/generate_test.go new file mode 100644 index 000000000000..a0a15e7046c3 --- /dev/null +++ b/src/app/itn_orchestrator/src/generate_test.go @@ -0,0 +1,254 @@ +package itn_orchestrator + +import ( + "math/rand" + "testing" +) + +func someParams() GenParams { + return GenParams{ + MinTps: 0.01, + BaseTps: 0.6, + StressTps: 1.4, + SenderRatio: 0.8, + ZkappRatio: 0.7, + NewAccountRatio: 1.5, + StopCleanRatio: 0.5, + MinStopRatio: 0.1, + MaxStopRatio: 0.25, + RoundDurationMin: 50, + PauseMin: 10, + Rounds: 48, + StopsPerRound: 1, + Gap: 100, + ExperimentName: "test", + Privkeys: []string{"key0"}, + PaymentReceiver: "B62qpPita1s7Dbnr7MVb3UK8fdssZixL1a4536aeMYxbTJEtRGGyS8U", + PrivkeysPerFundCmd: 2, + GenerateFundKeys: 20, + MixMaxCostTpsRatio: 0.7, + LargePauseEveryNRounds: 8, + LargePauseMin: 240, + MinBalanceChange: 1e3, + MaxBalanceChange: 3e3, + DeploymentFee: 1e9, + PaymentAmount: 1e5, + MinFee: 1e9, + MaxFee: 3e9, + FundFee: 1e9, + } +} + +type shuffledIxs []int + +func (ixs *shuffledIxs) Push(ix int) { + *ixs = append(*ixs, ix) +} + +func (ixs *shuffledIxs) Pop() int { + l := len(*ixs) + if l == 0 { + panic("unexpected pop") + } + i := rand.Intn(l) + res := (*ixs)[i] + (*ixs)[i] = (*ixs)[l-1] + *ixs = (*ixs)[:l-1] + return res +} + +type zkappGenState struct { + balances []int64 + availableDeployed shuffledIxs + availableNew shuffledIxs + queue []int + numNewAccounts int + conf ZkappCommandsDetails +} + +func newZkappGenState(pi ZkappCommandsDetails, numFeePayers int, balances []int64) zkappGenState { + availableDeployed := rand.Perm(pi.NumZkappsToDeploy) + for i := range availableDeployed { + availableDeployed[i] += numFeePayers + } + return zkappGenState{ + numNewAccounts: pi.NumNewAccounts, + balances: balances, + availableDeployed: availableDeployed, + conf: pi, + } +} + +func (state *zkappGenState) applyTx(numFeePayers, feePayerIx, accountUpdates, newAccounts, zkappAccounts int) { + state.numNewAccounts -= newAccounts + state.balances[feePayerIx] -= int64(state.conf.MaxFee) + availableDeployed := len(state.availableDeployed) + availableNew := len(state.availableNew) + tot := availableDeployed + availableNew + if tot <= 0 { + panic("not enough keys") + } + // TODO popped should be reused + popped := make([]int, 0, accountUpdates-newAccounts) + poppedCnt := make([]int, accountUpdates-newAccounts+1) + numNewPops := 0 + for i := 0; i < accountUpdates-newAccounts-zkappAccounts; i++ { + r := rand.Intn(tot) + if r < len(popped) { + poppedCnt[r]++ + numNewPops++ + } else if r < availableNew { + popped = append(popped, state.availableNew.Pop()) + numNewPops++ + } + } + numDeployedToPop := accountUpdates - newAccounts - numNewPops + 1 + newActuallyPopped := len(popped) + for i := 0; i < numDeployedToPop; i++ { + r := rand.Intn(availableDeployed) + newActuallyPopped + if r < len(popped) { + poppedCnt[r]++ + } else { + popped = append(popped, state.availableDeployed.Pop()) + } + } + balancing := popped[len(popped)-1] + poppedCnt[len(popped)-1]-- + state.queue = append(state.queue, popped...) + for i := 0; i < newAccounts; i++ { + ix := len(state.balances) + state.balances = append(state.balances, int64(state.conf.MinNewZkappBalance)-1e9) + state.queue = append(state.queue, ix) + } + if len(state.queue) > state.conf.AccountQueueSize { + prefix := len(state.queue) - state.conf.AccountQueueSize + for _, ix := range state.queue[:prefix] { + if ix < numFeePayers+state.conf.NumZkappsToDeploy { + state.availableDeployed.Push(ix) + } else { + state.availableNew.Push(ix) + } + } + state.queue = state.queue[prefix:] + } + for i, ix := range popped { + state.balances[ix] -= int64(state.conf.MaxBalanceChange*2) * int64(poppedCnt[i]+1) + } + state.balances[balancing] -= int64(accountUpdates*2-newAccounts)*int64(state.conf.MaxBalanceChange) + int64(newAccounts)*int64(state.conf.MaxNewZkappBalance) +} + +func testZkapp(t *testing.T, numFeePayers int, feePayerBalance int64, pi ZkappCommandsDetails) { + balances := make([]int64, numFeePayers+pi.NumZkappsToDeploy) + for i := 0; i < numFeePayers; i++ { + balances[i] = feePayerBalance + } + for j := 0; j < pi.NumZkappsToDeploy; j++ { + balances[j%numFeePayers] -= int64(pi.DeploymentFee + pi.InitBalance) + balances[numFeePayers+j] = int64(pi.InitBalance) - 1e9 + } + numTxs := int(float64(pi.DurationMin*60) * pi.Tps) + if pi.MaxCost { + for j := pi.NumZkappsToDeploy; j < numTxs; j++ { + balances[j%numFeePayers] -= int64(pi.MaxFee) + } + } else { + // TODO: move maxAccountUpdates to config, find a better name + // Transaction will be structured as: + // fee payer + balancing tx + (permission_setter + actual update) × maxAccountUpdates + maxAccountUpdates := 2 + state := newZkappGenState(pi, numFeePayers, balances) + for j := pi.NumZkappsToDeploy; j < numTxs; j++ { + accUpdates := rand.Intn(maxAccountUpdates) + 1 + newAccs := 0 + if state.numNewAccounts > 0 { + newAccs = rand.Intn(accUpdates + 1) + } + zkappAccs := 0 + for k := 0; k < accUpdates-newAccs; k++ { + if rand.Intn(3) == 0 { + zkappAccs++ + } + } + // t.Logf("%d:%d (%d new)", accUpdates*2+2, zkappAccs, newAccs) + state.applyTx(numFeePayers, j%numFeePayers, accUpdates, newAccs, zkappAccs) + } + balances = state.balances + } + for ix, b := range balances { + type_ := "new" + if ix < numFeePayers { + type_ = "fee payer" + } else if ix < numFeePayers+pi.NumZkappsToDeploy { + type_ = "zkapp" + } + if b < 0 { + t.Errorf("balance underflow %d for %s account", b, type_) + } + } + if t.Failed() { + t.Logf("Fee payer balance: %d", feePayerBalance) + t.Logf("Init zkapp balance: %d", pi.InitBalance) + t.Logf("New account balance: %d", pi.MaxNewZkappBalance) + } +} + +func testPayment(t *testing.T, numFeePayers int, feePayerBalance int64, pi PaymentsDetails) { + balances := make([]int64, numFeePayers) + for i := 0; i < numFeePayers; i++ { + balances[i] = feePayerBalance + } + numTxs := int(float64(pi.DurationMin*60) * pi.Tps) + for i := 0; i < numTxs; i++ { + balances[i%numFeePayers] -= int64(pi.Amount + pi.MaxFee) + } + for _, b := range balances { + if b < 0 { + t.Errorf("balance underflow %d for fee payer", b) + } + } + if t.Failed() { + t.Logf("Fee payer balance: %d", feePayerBalance) + } +} + +func TestGenerate(t *testing.T) { + for i := 0; i < 100; i++ { + params := someParams() + for r := 0; r < params.Rounds; r++ { + round := params.Generate(r) + var zkappParams *ZkappSubParams + var paymentParams *PaymentSubParams + for _, c := range round.Commands { + if c.Action == (ZkappCommandsAction{}).Name() { + p := (c.Params.(ZkappRefParams).ZkappSubParams) + zkappParams = &p + } else if c.Action == (PaymentsAction{}).Name() { + p := (c.Params.(PaymentRefParams).PaymentSubParams) + paymentParams = &p + } + } + if zkappParams != nil { + fund := *round.ZkappFundCommand + participants := int(zkappParams.Tps / zkappParams.MinTps) + tpsPerNode := zkappParams.Tps / float64(participants) + pi := ZkappPaymentsInput(*zkappParams, 0, tpsPerNode) + numFeePayers := fund.Num / participants + feePayerBalance := int64(fund.Amount) / int64(fund.Num) + for j := 0; j < 1000; j++ { + testZkapp(t, numFeePayers, feePayerBalance, pi) + } + } + if paymentParams != nil { + fund := *round.PaymentFundCommand + participants := int(paymentParams.Tps / paymentParams.MinTps) + tpsPerNode := paymentParams.Tps / float64(participants) + numFeePayers := fund.Num / participants + feePayerBalance := int64(fund.Amount) / int64(fund.Num) + pi := paymentInput(*paymentParams, 0, tpsPerNode) + for j := 0; j < 1000; j++ { + testPayment(t, numFeePayers, feePayerBalance, pi) + } + } + } + } +} diff --git a/src/app/itn_orchestrator/src/generator/main.go b/src/app/itn_orchestrator/src/generator/main.go index 1ebf07c3aeb8..2e16e9e0a5f6 100644 --- a/src/app/itn_orchestrator/src/generator/main.go +++ b/src/app/itn_orchestrator/src/generator/main.go @@ -158,7 +158,12 @@ func main() { for r := 0; r < p.Rounds; r++ { round := p.Generate(r) cmds = append(cmds, round.Commands...) - fundCmds = append(fundCmds, round.FundCommands...) + if round.PaymentFundCommand != nil { + fundCmds = append(fundCmds, *round.PaymentFundCommand) + } + if round.ZkappFundCommand != nil { + fundCmds = append(fundCmds, *round.ZkappFundCommand) + } } privkeys := p.Privkeys if p.GenerateFundKeys > 0 { diff --git a/src/app/itn_orchestrator/src/payments.go b/src/app/itn_orchestrator/src/payments.go index c530040a8c11..9267d50df983 100644 --- a/src/app/itn_orchestrator/src/payments.go +++ b/src/app/itn_orchestrator/src/payments.go @@ -39,8 +39,8 @@ func PaymentKeygenRequirements(gap int, params PaymentSubParams) (int, uint64) { return keys, balance } -func schedulePaymentsDo(config Config, params PaymentParams, nodeAddress NodeAddress, batchIx int, tps float64, feePayers []itn_json_types.MinaPrivateKey) (string, error) { - paymentInput := PaymentsDetails{ +func paymentInput(params PaymentSubParams, batchIx int, tps float64) PaymentsDetails { + return PaymentsDetails{ DurationMin: params.DurationMin, Tps: tps, MemoPrefix: fmt.Sprintf("%s-%d", params.ExperimentName, batchIx), @@ -48,8 +48,12 @@ func schedulePaymentsDo(config Config, params PaymentParams, nodeAddress NodeAdd MinFee: params.MinFee, Amount: params.Amount, Receiver: params.Receiver, - Senders: feePayers, } +} + +func schedulePaymentsDo(config Config, params PaymentSubParams, nodeAddress NodeAddress, batchIx int, tps float64, feePayers []itn_json_types.MinaPrivateKey) (string, error) { + paymentInput := paymentInput(params, batchIx, tps) + paymentInput.Senders = feePayers handle, err := SchedulePaymentsGql(config, nodeAddress, paymentInput) if err == nil { config.Log.Infof("scheduled payment batch %d with tps %f for %s: %s", batchIx, tps, nodeAddress, handle) @@ -67,7 +71,7 @@ func SchedulePayments(config Config, params PaymentParams, output func(Scheduled for nodeIx, nodeAddress := range nodes { feePayers := remFeePayers[:feePayersPerNode] var handle string - handle, err = schedulePaymentsDo(config, params, nodeAddress, len(successfulNodes), tps, feePayers) + handle, err = schedulePaymentsDo(config, params.PaymentSubParams, nodeAddress, len(successfulNodes), tps, feePayers) if err != nil { config.Log.Warnf("error scheduling payments for %s: %v", nodeAddress, err) n := len(nodes) - nodeIx - 1 @@ -88,7 +92,7 @@ func SchedulePayments(config Config, params PaymentParams, output func(Scheduled if err != nil { // last schedule payment request didn't work well for _, nodeAddress := range successfulNodes { - handle, err2 := schedulePaymentsDo(config, params, nodeAddress, len(successfulNodes), tps, remFeePayers) + handle, err2 := schedulePaymentsDo(config, params.PaymentSubParams, nodeAddress, len(successfulNodes), tps, remFeePayers) if err2 != nil { config.Log.Warnf("error scheduling second batch of payments for %s: %v", nodeAddress, err2) continue diff --git a/src/app/itn_orchestrator/src/zkapp.go b/src/app/itn_orchestrator/src/zkapp.go index b288aea0e522..fe85b5730d96 100644 --- a/src/app/itn_orchestrator/src/zkapp.go +++ b/src/app/itn_orchestrator/src/zkapp.go @@ -49,7 +49,7 @@ func ZkappBalanceRequirements(tps float64, p ZkappSubParams) (int, uint64, uint6 maxExpectedUsagesPerNewAccount := float64(p.DurationMin*60) / float64(p.Gap) //We multiply by three because by matter of chance some zkapps may generate more new accounts newAccountsPerZkapp := int(float64(newAccounts) / float64(totalZkappsToDeploy) * 3) - balanceDeductedPerUsage := p.MaxBalanceChange * 8 + balanceDeductedPerUsage := p.MaxBalanceChange * 2 // We add 1e9 because of account creation fee minNewZkappBalance := uint64(maxExpectedUsagesPerNewAccount*float64(balanceDeductedPerUsage))*3 + 1e9 maxNewZkappBalance := minNewZkappBalance * 3 @@ -73,16 +73,15 @@ func ZkappKeygenRequirements(initZkappBalance uint64, params ZkappSubParams) (in return keys, balance } -func zkappPaymentsInput(params ZkappCommandParams, batchIx int, tps float64, feePayers []itn_json_types.MinaPrivateKey) ZkappCommandsDetails { - zkappsToDeploy, accountQueueSize := zkappParams(params.ZkappSubParams, tps) - newAccounts, minNewZkappBalance, maxNewZkappBalance, initBalance := ZkappBalanceRequirements(tps, params.ZkappSubParams) +func ZkappPaymentsInput(params ZkappSubParams, batchIx int, tps float64) ZkappCommandsDetails { + zkappsToDeploy, accountQueueSize := zkappParams(params, tps) + newAccounts, minNewZkappBalance, maxNewZkappBalance, initBalance := ZkappBalanceRequirements(tps, params) return ZkappCommandsDetails{ MemoPrefix: fmt.Sprintf("%s-%d", params.ExperimentName, batchIx), DurationMin: params.DurationMin, Tps: tps, NumZkappsToDeploy: zkappsToDeploy, NumNewAccounts: newAccounts, - FeePayers: feePayers, NoPrecondition: params.NoPrecondition, MinBalanceChange: params.MinBalanceChange, MaxBalanceChange: params.MaxBalanceChange, @@ -98,7 +97,8 @@ func zkappPaymentsInput(params ZkappCommandParams, batchIx int, tps float64, fee } func scheduleZkappCommandsDo(config Config, params ZkappCommandParams, nodeAddress NodeAddress, batchIx int, tps float64, feePayers []itn_json_types.MinaPrivateKey) (string, error) { - paymentInput := zkappPaymentsInput(params, batchIx, tps, feePayers) + paymentInput := ZkappPaymentsInput(params.ZkappSubParams, batchIx, tps) + paymentInput.FeePayers = feePayers handle, err := ScheduleZkappCommands(config, nodeAddress, paymentInput) if err == nil { config.Log.Infof("scheduled zkapp batch %d with tps %f for %s: %s", batchIx, tps, nodeAddress, handle)