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

Pseudo-random generator statistical tests #4

Merged
merged 56 commits into from
Nov 8, 2023
Merged

Conversation

sisyphusSmiling
Copy link
Contributor

@sisyphusSmiling sisyphusSmiling commented Oct 26, 2023

Closes: #3

Introduces statistical tests for the xorshift128+ implementation in PseudoRandomGenerator contract, asserting a uniform distribution across a sample of UInt64 random results.

@sisyphusSmiling sisyphusSmiling self-assigned this Oct 26, 2023
@sisyphusSmiling sisyphusSmiling changed the title [WIP] Pseudo random generator statistical tests Pseudo-random generator statistical tests Oct 28, 2023
@sisyphusSmiling sisyphusSmiling marked this pull request as ready for review October 28, 2023 00:19
@sisyphusSmiling sisyphusSmiling requested a review from a team October 28, 2023 00:20
Copy link
Collaborator

@janezpodhostnik janezpodhostnik left a comment

Choose a reason for hiding this comment

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

I can only review the go code, and that looks good. But i don't know if this is the proper structure for this sort of project.

flow.json Outdated Show resolved Hide resolved
lib/go/templates/go.mod Outdated Show resolved Hide resolved
Copy link
Member

@joshuahannan joshuahannan left a comment

Choose a reason for hiding this comment

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

If we're going to create new test suites, it would probably be better to use the cadence testing framework or overflow so we don't have to continue to rely on the poorly organized test suites from the other repos any more. Are either of those possible?

lib/go/contracts/contracts.go Outdated Show resolved Hide resolved
@sisyphusSmiling
Copy link
Contributor Author

@joshuahannan Agreed, I'll build out tests for the other contracts & their scripts using the Cadence testing framework. The PRG test needs to be done a non-Cadence framework since we need to run an analysis on the statistical distribution of random numbers returned from the PRG. Go is preferable here given the supporting BasicDistributionTest() method in flow-go and was raised by @tarakby in #3

@joshuahannan
Copy link
Member

Are we able to use overflow though? that is much better design framework than my tests

@sisyphusSmiling
Copy link
Contributor Author

I'd imagine so. I'm not familiar with it, but can look into it for the test setup & execution. Better for me to familiarize myself with it since it's been mentioned as a powerful tool.

@sisyphusSmiling
Copy link
Contributor Author

sisyphusSmiling commented Oct 30, 2023

Quick update: the PRG stat tests now use overflow as suggested by @joshuahannan. Since overflow pulls the contract config from local flow.json, I've moved the test & its helper to the root dir where the project's flow.json is located. And since we're using overflow, we no longer need the contract bindata or templates, so the lib/go/ directory containing both contracts/ and templates/ has been removed.

Once this PR is done and merged, I can move on to building out Cadence unit tests.

@tarakby
Copy link
Collaborator

tarakby commented Oct 31, 2023

Looking at this PR shortly, apologies for the delay

prg_test.go Outdated
classWidth := (math.MaxUint64 / uint64(n)) + 1

// using the same seed, the salt varies in getNextUInt64()
seed := GetRandomSeed(t)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The uniformity checked in this test is different from the uniformity that we expect to test when assessing a PRG quality : this test looks at the the uniformity of the first generated output across multiple PRG instances (diversified per salt), the quality of a PRG is evaluated using successive outputs of the same PRG instance (same seed and salt).
A PRG that succeeds the first criteria may fail the second (the main quality one).

I suggest two things:

  • update the current test to use the same PRG instance and evaluate the uniformity of it successive outputs.
  • add another test, to make sure both the seed and salt are taken into account in the implementation. This doesn't need to be a statistical test. It's enough to check that two instances with different seeds but same salt output different randoms (the first output is enough to check), and then the same for 2 instances with the same seed but different salts.

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 is really helpful, thank you! I'll make these changes shortly.

go.mod Outdated
require (
github.com/bjartek/overflow v1.14.1
github.com/onflow/cadence v0.42.2
github.com/onflow/flow-go/crypto v0.24.9
Copy link
Collaborator

Choose a reason for hiding this comment

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

I made a recent PR to improve the statistical tests (onflow/flow-go#4844). It can help reduce flakiness in the test. Maybe I can merge that PR and tag a new version to be used here first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. I'll bump the version once it's ready to go.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I just merged the PR and tagged a new version v0.24.10 that you can use

prg_test_helpers.go Show resolved Hide resolved
@sisyphusSmiling
Copy link
Contributor Author

sisyphusSmiling commented Nov 8, 2023

Quick update - PR should be ready for another round of reviews. Some notes on changes:

  • PRG implementation has been updated with changes from Update PRG interface and implementation #6
  • PRG initialization had a bug created during previous refactor and has been corrected after discovery while testing
  • Generating sufficient quantity of random numbers for BasicDistributionTest required persisting PRG state across a number of transactions (due to computational limits) and recording generated random numbers. In order to do this, I introduced a simple helper contract for this express purpose - RandomResultStorage. As suggested by @tarakby in this comment, I first generated the numbers, then query the contract to retrieve the generated results - both steps done in batches to prevent reaching computational limits. These results are then passed to BasicDistributionTest to assess uniformity of distribution
  • Two additional test cases have been added, covering alternate random results from same seed + different salts and different seeds + same salt

Note that the sample size assignment in the result distribution test had to be hardcoded due to its calculation in BasicDistributionTest. This is not ideal as it creates a fragile test, breaking if/when the sample size constants are updated in crypto/random/rand_utils. Open to suggestions on making this more robust.

Copy link
Collaborator

@tarakby tarakby left a comment

Choose a reason for hiding this comment

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

Nice and clean work in such a short time 👌🏼 Thank you!

I didn't think of the complexity of having to batch the queries (and the txs and scripts that had to be implemented). I suggest to merge the PR in its current state, and that we continue to think of a way to simplify the tests. We can add/modify the tools in crypto/random if that helps.
In particular I'm thinking if there is a way to make a transaction return a value somehow (maybe through the transaction results?).
Suggestions on simplifying the tests are welcome!

let receipt <- signer.load<@CoinToss.Receipt>(from: CoinToss.ReceiptStoragePath)
?? panic("No Receipt found!")

// Reveal by redeeming my receipt
// Reveal by redeeming my receipt - fingers crossed!
Copy link
Collaborator

Choose a reason for hiding this comment

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

🤞🏼

prg_test_helpers.go Outdated Show resolved Hide resolved
// calls a script which creates a new PRG struct from the given seed and
// random salt, gets the next uint64 and destroys the prg resource before
// returning the next uint64
func GetNextUInt64NewPRGRandSalt(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this function isn't used and can be deleted


// get the results in batches again due to query computational limit
results := make([]uint64, sampleSize)
ProcessBatches(sampleSize, maxBatchSize, func(startIdx, batchSize int) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

apologies, my suggestion on how to structure the test wasn't simple to implement! I missed the computation limit on both the transaction (generating the randoms) and script (reading the randoms), and didn't think we may need to perform that in batches.
Nice and clean implementation 👌🏼

var sampleSize int

// taken from BasicDistributionTest constants - hardcoding here is
/// fragile, but it's determined within flow-go/crypto/rand_utils
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree this part to get the sampleSize isn't ideal, I also missed that this should have been exported by the tools in flow-go/crypto/random just like BasicDistributionTest.

@tarakby
Copy link
Collaborator

tarakby commented Nov 8, 2023

My last comment is about the repo structure and whether developers can be confused about what’s a test, example and production code. For instance /contracts contains 2 production contracts (history + xorshift) and 1 example (cointoss). Same for /transaction where there are also test transactions. I’m wondering if it’s better to have a /test subfolder and /example subfolder under /contracts , /transactions and /scripts , or another way to clear the possible confusion.

Co-authored-by: Tarak Ben Youssef <[email protected]>
@sisyphusSmiling
Copy link
Contributor Author

Thank you @tarakby!

My last comment is about the repo structure and whether developers can be confused about what’s a test, example and production code. For instance /contracts contains 2 production contracts (history + xorshift) and 1 example (cointoss). Same for /transaction where there are also test transactions. I’m wondering if it’s better to have a /test subfolder and /example subfolder under /contracts , /transactions and /scripts , or another way to clear the possible confusion.

Good point. Just to make sure main is updated ASAP, I'll merge PR as-is and create a fast-follow PR with a directory restructure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PRG implementation testing
4 participants