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

Define Execution Run Method to Compute Machine Hashes With Step Size for BOLD #2392

Merged
merged 27 commits into from
Jul 11, 2024

Conversation

rauljordan
Copy link
Contributor

Background

As part of Arbitrum BOLD, validators will need to ask a stateless execution server for a list of hashes of executing an Arbitrator machine with some configuration. Validators can specify the start index at which to load the machine, how many times to step over it, and the step size in its execution. For instance:

"I'm in a BOLD challenge, and I need to get the hashes of executing the machine for message number 70, starting at program counter 10, and stepping through it in steps of size 1M. Give me 1024 hashes from that computation"

This PR keeps the implementation simple and adds unit tests to check the invariants of the function. This function is unused until BOLD is merged into Nitro

@cla-bot cla-bot bot added the s Automatically added by the CLA bot if the creator of a PR is registered as having signed the CLA. label Jun 13, 2024
validator/server_arb/execution_run.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run.go Show resolved Hide resolved
validator/server_arb/execution_run.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run.go Outdated Show resolved Hide resolved
@rauljordan
Copy link
Contributor Author

Ready again @tsahee thanks for the great review!

@rauljordan rauljordan requested a review from tsahee June 20, 2024 12:51
@rauljordan rauljordan requested a review from eljobe June 20, 2024 16:24
Copy link
Member

@eljobe eljobe left a comment

Choose a reason for hiding this comment

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

This is probably mostly issues with my ignorance of exactly why we're trying to do what we're trying to do. But, none-the-less, I find myself with questions as I read the code. So, I'm commenting on them.

@@ -126,6 +126,10 @@ func (r *mockExecRun) GetStepAt(position uint64) containers.PromiseInterface[*va
}, nil)
}

func (r *mockExecRun) GetMachineHashesWithStepSize(machineStartIndex, stepSize, numRequiredHashes uint64) containers.PromiseInterface[[]common.Hash] {
Copy link
Member

Choose a reason for hiding this comment

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

Is numRequiredHases essentially numIterations?

That is, you're telling the machine to start at position x, and then perform y iterations of s opcodes per iteration.
The fact that this also produces y hashcodes (one for each iteration) is more related to the return value than the arguments. Right?

The reason I'm making a big deal of the naming, is that I actually was confused when I first read the signature. numRequiredHashes sounds like it might be independent from how far through the machine execution progresses. But, I don't think it actually is. Without a doc comment, or something explicit, it isn't clear that these "required hashes" are the machine hash after each iteration of size stepSize.

Also, "required" sort of makes me think that it's possible that the function will error out because it wasn't able to provide enough hashes, or that it should fill in the returned slice with some "filler" hash if the machine finishes before enough hashes are generated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great feedback! Renamed to num iterations

validator/server_arb/execution_run.go Show resolved Hide resolved
return machineHashes, nil
}

logInterval := requiredNumHashes / 20 // Log every 5% progress
Copy link
Member

Choose a reason for hiding this comment

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

Optional: Maybe this should be configurable? It seems like something we might want to be more noisy in the beginning, but then turn way down low later?

validator/server_arb/execution_run.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run.go Show resolved Hide resolved
e := &executionRun{}
ctx := context.Background()

t.Run("basic argument checks", func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

Why are all these different test cases being run using t.Run instead of just being separate top-level test functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Personal preference here. I find it more expressive to write subcases in plain english instead of having to wrangle the Go function naming convention. It also groups functionality into one place, but I'm happy to change it if the alternative is highly preferred

Copy link
Contributor

Choose a reason for hiding this comment

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

I looked in the CI and saw that it does display these runs separately and nicely.
The advantage of separate functions is that you can run one by itself, and out CI will re-execute every failed test once to see if it first failed due to system flakiness.
I think for short low-footprint tests like these using t.Run is fine, and for anything in system_test I would still prefer separate functions.

validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
}
}
})
t.Run("if finishes execution early, simply pads the remaining desired hashes with the machine finished hash", func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

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

Well, this answers my earlier question. Why is this the protocol? Why not just return fewer hashes if the machine finishes? Seems like it could reduce the size of the data passed back to the callers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed...will let the caller pad if they need to

@rauljordan
Copy link
Contributor Author

Ready again @eljobe , thanks for the review

eljobe
eljobe previously approved these changes Jun 24, 2024
Copy link
Member

@eljobe eljobe left a comment

Choose a reason for hiding this comment

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

LGTM

validator/valnode/validation_api.go Outdated Show resolved Hide resolved
@rauljordan
Copy link
Contributor Author

Hi @eljobe thanks again! Needs reapproval after the last naming change

Copy link
Contributor

@tsahee tsahee left a comment

Choose a reason for hiding this comment

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

the only comment that I think of as blocking is creating bock machine / execution run per internal test instead of sharing them.
Great work.

system_tests/validation_mock_test.go Outdated Show resolved Hide resolved
if err := machine.Step(ctx, stepSize); err != nil {
return nil, fmt.Errorf("failed to step machine to position %d: %w", absoluteMachineIndex, err)
}
if i%logInterval == 0 || i == maxIterations-1 {
Copy link
Contributor

Choose a reason for hiding this comment

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

not a must..
I found that logging every x seconds (remembering when you last logged and logging again if enough time passed) is better than logging every X iterations

e := &executionRun{}
ctx := context.Background()

t.Run("basic argument checks", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I looked in the CI and saw that it does display these runs separately and nicely.
The advantage of separate functions is that you can run one by itself, and out CI will re-execute every failed test once to see if it first failed due to system flakiness.
I think for short low-footprint tests like these using t.Run is fine, and for anything in system_test I would still prefer separate functions.

validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
validator/server_arb/execution_run_test.go Outdated Show resolved Hide resolved
@rauljordan rauljordan requested a review from tsahee July 8, 2024 15:20
@rauljordan
Copy link
Contributor Author

Ready again @tsahee , thank you !

Copy link
Contributor

@tsahee tsahee left a comment

Choose a reason for hiding this comment

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

LGTM

@tsahee tsahee enabled auto-merge July 10, 2024 23:51
@tsahee tsahee merged commit 95255eb into master Jul 11, 2024
12 checks passed
@tsahee tsahee deleted the get-machine-hashes-with-step branch July 11, 2024 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design-approved s Automatically added by the CLA bot if the creator of a PR is registered as having signed the CLA.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants