Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tools: block generator apps. Part 2: boxes #5478

Merged
merged 8 commits into from Jun 27, 2023

Conversation

tzaffi
Copy link
Contributor

@tzaffi tzaffi commented Jun 17, 2023

Summary

Enable opting in and calling box apps and populate database table app_box which was the last remaining table that wasn't populated.

Changes

  • block-generator arguments:
    • rename --log-level to --conduit-log-level
    • new argument --verbose
  • tools/block-generator/generator/config.go:
    • make previously introduced types into Stringers
  • tools/block-generator/generator/generate.go:
    • handle the fact that a requested transaction may end up with many multiple side effects
    • keep track of pending app and committed app information via:
      • gen.appMap
      • gen.appSlice
      • gen.pendingAppMap
      • gen.pendingAppSlice
    • introduce gen.startRound() mostly to keep the generator's txnCounter in sync with the Ledger's
      • in particular numTxnForBlock := g.txnForRound(g.round) is no longer the fixed block size but instead becomes a LOWER BOUND
      • NOTE: for the upcoming Part 3, this can become quite distorted as a single swap app call generates a total of 256 transacations
    • handle the dependency between the three box app transaction types:
      • app call can only happen after app optin which can only happen after app create
    • introduce gen.introspectLedgerVsGenerator() which is only called under --verbose and prints out debugging information comparing the generator's state with its ledger's stat.
  • tools/block-generator/generator/make_transactions.go:
    • makeAppCreateTxn() - handles an additional pay txn to fund the app in the case of creating a boxes app
    • introducing makeAppOptinTxn() which also:
      • pays 1 algo to fund the new box (MBR=0.412 Algos) via a sibling txn
      • creates the box with
        • key = sender's address
        • value = "z" * 992 (pythonese)
      • has 2 inner pay txns
    • introducing makeAppCallTxn() which is a ABI call that logs the box contents
  • tools/block-generator/generator/teal/poap_boxes.teal
    • adding commentary for better readability
    • adding a box creation of size 1K during opt-in
  • new and modified scenarios:
    • conforming to naming convention config.XYZ.(small|jumbo).yml
    • new scanarios:
      • config.allmixed.jumbo.yml - includes all kinds of transactions
      • config.allmixed.small.yml
      • config.appboxes.jumbo.yml

TODO's ...

...which were inherited from PR Part 1

...which have been created in this PR

  • remove all explicit ApplyData settings/params as Ledger.Eval() should be in charge of this now and all existing references are empty (generator.go)
  • return the number of transactions generated instead of updating intra (generator.go)
  • remove type appData struct field kind if still unused after Part 3 (generator_types.go)
  • add sanity checks similar to the one for the case appTxTypeCreate for the other cases (generateAppCallInternal())
  • "these may not make sense for the swap optin" (make_transactions.go)
  • need to update explanatory comment for the effects map in the next PR for because of the new teal files and updated names
  • the values of the effects map should be slices of a type different from TxTypeID
  • the generator only uses effects map to update txnCounter (or intra)
  • there is a unit test that asserts the total number of transactions for each TxTypeID with effects is exactly as expected
  • the effects are only used at report time in run.go to split out the various effects
  • introduce generate_apps.go for application specific generator code
  • introduce generate_ledger.go for ledger specific generator code
  • figure out if there is some simple code organization to help with handling the dependencies between states of applications. Maybe we can plan for some larger architecture changes in the future (i.e. using the ledger, it wasn't available in earlier versions of this tool).

Test Plan

Locally test as follows:

cd tools/block-generator

# start postgres in a docker container:
❯ make debug-blockgen
...

# run a 30 second "jumbo" experiment mixed with all kinds of transactions 50% of which are app related:
❯ make run-runner SCENARIO=scenarios/config.allmixed.jumbo.yml DURATION=30s VERBOSE=--verbose
... lots of round info play-by-play that will partially look like:
...
Received round request 0, but nextRound=1. Not finishing round.
--------------------
roundNumber (generator): 1
round (ledger): 1
g.txnCounter + intra: 101000
... etc ...

# generator/conduit's log and report location:
❯ ls ../../tmp/RUN_RUNNER_OUTPUTS
config.allmixed.jumbo.conduit-log config.allmixed.jumbo.report      config.allmixed.jumbo_data

# introspect the database
❯ make enter-pg # or you might just call `psql` directly if you prefer to not enter the pg container
generator_db=# \dt
               List of relations
...

generator_db=# select count(*) from block_header;
...

generator_db=# select count(*) from txn;
...

@tzaffi tzaffi changed the title tools: block gerarter apps. Part 2: boxes tools: block generator apps. Part 2: boxes Jun 17, 2023
tzaffi

This comment was marked as resolved.

@tzaffi tzaffi marked this pull request as ready for review June 20, 2023 16:09
@tzaffi tzaffi requested a review from a team June 20, 2023 16:10
tools/block-generator/Makefile Show resolved Hide resolved
tools/block-generator/README.md Show resolved Hide resolved
tools/block-generator/generator/generate.go Outdated Show resolved Hide resolved
var effects map[TxTypeID][]TxEffect

func init() {
effects = map[TxTypeID][]TxEffect{

This comment was marked as outdated.

tzaffi and others added 2 commits June 21, 2023 10:56
@codecov
Copy link

codecov bot commented Jun 21, 2023

Codecov Report

Merging #5478 (676db87) into master (91185ae) will increase coverage by 0.33%.
The diff coverage is n/a.

@@            Coverage Diff             @@
##           master    #5478      +/-   ##
==========================================
+ Coverage   55.30%   55.63%   +0.33%     
==========================================
  Files         446      446              
  Lines       63153    63153              
==========================================
+ Hits        34924    35135     +211     
+ Misses      25864    25641     -223     
- Partials     2365     2377      +12     

see 25 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@shiqizng shiqizng requested review from winder and a team June 22, 2023 19:14
Comment on lines 29 to 30
block-generator: clean
go build
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this to the first target so that make with no arguments builds the binary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +73 to +75
// Special TxTypeID's recording effects of higher level transactions
effectPaymentTxSibling TxTypeID = "effect_payment_sibling"
effectInnerTx TxTypeID = "effect_inner_tx"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these configurable? I believe the TxTypeID was previously used for the different types of transactions that would be chosen according to the ratios. This seems more like something that would be in GenerationConfig like tx_per_block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right that it's better to keep TxTypeID for types of transactions that can actually be chosen. I'll work to carve out a separate types for these effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, one issue here is that I do need to record the effects and these get stored in a type Report map[TxTypeID]TxData whose key is of type TxTypeID. So it does complicates things a bit. If we relax Report to have keys of type string, I still ought to be able to separate it out. I'll keep you posted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original concern will be addressed in a follow up PR. Here's a commit: tzaffi@291d30f

g.txnCounter += txnCount
// startRound updates the generator's txnCounter based on the latest block header.
// It is assumed that g.round has already been incremented in finishRound()
func (g *generator) startRound() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we unable to manually track the txn counter? There should be a known number of inner transactions. Does it match how many the ledger things there are?

Copy link
Contributor Author

@tzaffi tzaffi Jun 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great question and deserves a good answer. I'll investigate a little further.

Copy link
Contributor Author

@tzaffi tzaffi Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some investigation, I believe it would be difficult to achieve this without some significant downsides. This involves opening up BlockEvaluator.Eval() to get the TxnCounter directly from the evaluator. There is -in fact- a method BlockEvaluator.TestingTxnCounter() which is meant for this purpose. However, accessing it means interacting with the BlockEvaluator that's created during the ledger's call to eval.Eval(). One relatively non-invasive way I see of achieving this is to modify interface EvalTracer's AfterBlock() to take in a new parameter newTxnCounter that will let us trace this value and then use it in the new method that I'm introducing ledgerAddBlock(). This feels like overkill to me though. Notice that the same method does introduce an improvement: we calculate the number of transactions in the block by looking at all the actual transactions including inners that were evaluated.

Copy link
Contributor Author

@tzaffi tzaffi Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading a bit more carefully the original comment, it's arguable that the follow up PR DOES address the concern as it:

  1. gets the theoretical transaction count using effects
  2. gets the actual transaction count from ledgerAddBlock()
  3. compares them and errors if they're unequal

see:
https://github.com/tzaffi/go-algorand/blob/f631aec855695e0e984008bcafd1041a46fa0a15/tools/block-generator/generator/generate.go#L382-L388

In a perfect world, we wouldn't have to re-count the transactions and get them directly from the block evaluator, but I think this is good enough going forward.

g.timestamp += consensusTimeMilli
g.round++

// Apply pending assets...
g.assets = append(g.assets, g.pendingAssets...)
g.pendingAssets = nil

// Apply pending apps...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment with some more explanation about whats going on here would be helpful.

I don't think the loops and cleverness around maps & kinds are worth it here. It hurts clarity and I'm not even sure if it's shorter code.

Did you do it this way because you're imagining lots of additional kinds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic around keeping track of the state of each application in the ledger is the most complex part of this PR. It's about to get significantly more complex as being able to generate an AMM swap requires 7 setup steps prior and therefore much more ability to keep track of state. It's worth a discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's figure out if there is some simple code organization to help with that for now. Maybe we can plan for some larger architecture changes in the future (i.e. using the ledger, it wasn't available in earlier versions of this tool).

It isn't production code, so introducing tech debt here is more acceptable than some of our other systems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This depends on where the AMM ends up, so I would defer as a future discussion. I've created a TODO for this.

winder
winder previously approved these changes Jun 22, 2023
Copy link
Contributor

@winder winder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran the generator and see boxes. Nice!

I'm OK merging this in even if there is followup work.

//
// appBoxesCreate: 1 sibling payment tx
// appBoxesOptin: 1 sibling payment tx, 2 inner tx
var effects map[TxTypeID][]TxEffect = map[TxTypeID][]TxEffect{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
var effects map[TxTypeID][]TxEffect = map[TxTypeID][]TxEffect{
var effects = map[TxTypeID][]TxEffect{

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -93,8 +120,19 @@ func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config Generat
gen.genesisHash = bkGenesis.Hash()
}

gen.apps = make(map[appKind][]*appData)
gen.pendingApps = make(map[appKind][]*appData)
gen.resetPendingApps()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think resetPendingApps is needed here. could you also remind me why we need both appSlice and appMap?

Copy link
Contributor Author

@tzaffi tzaffi Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appSlice is used to pick a random app. appMap is used for $O(1)$ lookup a specific up to tell if an app exists and get its information. Otherwise it would be $O(N)$ where $N$ is the number of created apps.

Copy link
Contributor Author

@tzaffi tzaffi Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetPendingApps() also sets gen.PendingApps[of each kind] and gen.PendingAppMap[of each kind] as we're appending to nil maps of other types panics (as opposed to appending to a nil slice)

Copy link
Contributor

@shiqizng shiqizng Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tzaffi could we move some methods to helpers file or a file that's just for app scenario? this file is getting large and it's harder to review.

Copy link
Contributor Author

@tzaffi tzaffi Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The future follow up PR will introduce the following files to break up the code a bit:

I've also introduced TODO's for this PR's description

tools/block-generator/generator/generate.go Outdated Show resolved Hide resolved
@@ -763,110 +876,242 @@ func (g *generator) generateAssetTxnInternalHint(txType TxTypeID, round uint64,
fmt.Printf("\n\nthe sender account does not have enough algos for the transfer. idx %d, asset transaction type %v, num %d\n\n", senderIndex, actual, g.reportData[actual].GenerationCount)
os.Exit(1)
}

if assetID == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when do you expect this to happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only happens if the assetID didn't get assigned correctly. That is, if I introduced a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add "this should never happen"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Co-authored-by: shiqizng <80276844+shiqizng@users.noreply.github.com>
// TODO: should check for min balance}
g.balances[senderIndex] -= (pstFee + pstAmt)

return txntest.Group(&createTxn, &paySibTxn)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description you mention that this transaction has two inner transactions. Are you referring to the transaction group containing two transactions?

Looking at blocks generated by the daemon I see pairs of appl/pay transactions but they do not create any inner transactions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The future followup PR exposes the inner transactions and even prints out a count in verbose mode.

# transaction distribution
# tx_pay_fraction: 0.01
# tx_asset_fraction: 0.01
# tx_app_fraction: 0.98
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can these lines be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if err != nil {
panic(fmt.Sprintf("failed to generate transaction: %v\n", err))
// return err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// return err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorporated in future PR. Commit: f631aec

panic(fmt.Sprintf("failed to encode transaction: %v\n", err))
if len(signedTxns) == 0 {
return fmt.Errorf("failed to generate transaction: no transactions given")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line can be removed? the next condition, if len(signedTxns) != len(ads), should be sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future PR, this line is being kept, but the other condition if len(signedTxns) != len(ads) is going away because there are no ads any more.

@@ -93,8 +120,19 @@ func MakeGenerator(dbround uint64, bkGenesis bookkeeping.Genesis, config Generat
gen.genesisHash = bkGenesis.Hash()
}

gen.apps = make(map[appKind][]*appData)
gen.pendingApps = make(map[appKind][]*appData)
gen.resetPendingApps()
Copy link
Contributor

@shiqizng shiqizng Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tzaffi could we move some methods to helpers file or a file that's just for app scenario? this file is getting large and it's harder to review.

@shiqizng
Copy link
Contributor

I'm ok with having a follow up PR to address the TODOs. I believe this PR works as intended. I ran the appboxes scenario locally and saw the rows in app_box table.

@winder winder merged commit 74b5f19 into algorand:master Jun 27, 2023
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants