node: Update accountant audit logic - #4947
Conversation
|
Claude finished @djb15's task in 4m 39s —— View job Code Review: Update accountant audit logic
Overall: This PR looks good. The implementation is well-structured, the size model is correct, and the tests are thorough. No blocking issues found. SummaryThe PR replaces the previous fixed-count batching (up to 100 observations per batch, regardless of serialized size) with size-aware packing that ensures each batch's marshaled What I verifiedSize model correctness: I traced the size computation end-to-end:
Bin packing algorithm: First-fit packing ( Refactoring: The Metrics: Two new Prometheus counters ( Tests: The test suite ( FindingsTwo low-severity inline comments posted — no blocking issues. |
| batchSizeSplits.Inc() | ||
| } | ||
| for _, msg := range oversized { | ||
| // The message is submitted anyway (alone, so it can't take any other observations down with it) in case our size accounting is too conservative. |
There was a problem hiding this comment.
Should we add a log in the case where an oversized message does not fail? This would help give us a signal to change the limit.
There was a problem hiding this comment.
Also do they still get submitted? The loop below seems to only submit messages under batches, not oversized
|
|
||
| const ( | ||
| DefaultSubmitObservationBatchSize = 100 // Observations per batch (limited by wasm contract input size of 64KB) | ||
| DefaultSubmitObservationBatchSize = 100 // Maximum observations per batch, also subject to maxSubmitObservationsMsgSize |
There was a problem hiding this comment.
Could you change this to be uint8 (rather than the default int)?
There was a problem hiding this comment.
We don't use the non default int type for constants in most other places in the Guardian, why the ask to change that?
| const ( | ||
| DefaultSubmitObservationBatchSize = 100 // Observations per batch (limited by wasm contract input size of 64KB) | ||
| DefaultSubmitObservationBatchSize = 100 // Maximum observations per batch, also subject to maxSubmitObservationsMsgSize | ||
| maxSubmitObservationsMsgSize = 64 * 1024 // Maximum size of the marshaled submit_observations message (the wasm contract input is limited to 64KB) |
There was a problem hiding this comment.
Could you change this to be e.g. uint16?
| // so a large message that does not fit in the current batch is deferred to a later batch rather than failing the messages | ||
| // around it. A message too large to fit even in a batch by itself is returned in oversized as well as being placed in its own | ||
| // batch. | ||
| func packObservationBatches(msgs []*common.MessagePublication, maxBatchCount int, maxMsgSize int) (batches [][]*common.MessagePublication, oversized []*common.MessagePublication) { |
There was a problem hiding this comment.
| func packObservationBatches(msgs []*common.MessagePublication, maxBatchCount int, maxMsgSize int) (batches [][]*common.MessagePublication, oversized []*common.MessagePublication) { | |
| func packObservationBatches(msgs []*common.MessagePublication, maxBatchCount uint8, maxMsgSize uint16) (batches [][]*common.MessagePublication, oversized []*common.MessagePublication) { |
We can use stricter types here to make it impossible to accidentally call these with negative values, or values that would be too big to make sense.
| // batch. | ||
| func packObservationBatches(msgs []*common.MessagePublication, maxBatchCount int, maxMsgSize int) (batches [][]*common.MessagePublication, oversized []*common.MessagePublication) { | ||
| // batchSizes[i] is the size the marshaled observations array for batches[i] would have, including the enclosing brackets. | ||
| var batchSizes []int |
There was a problem hiding this comment.
| var batchSizes []int | |
| var batchSizes []uint16 |
| return submitObservationsMsgOverhead + jsonQuotesSize + base64.StdEncoding.EncodedLen(obsArraySize) | ||
| } | ||
|
|
||
| // submitObservationsMsgOverhead is the number of bytes in a marshaled submit_observations message excluding the base64-encoded |
There was a problem hiding this comment.
Is this value a constant ultimately? Maybe we could pre-compute the value and use unit tests to lock it in.
| func makeObservation(msg *common.MessagePublication) Observation { | ||
| return Observation{ | ||
| TxHash: msg.TxID, | ||
| Timestamp: uint32(msg.Timestamp.Unix()), // #nosec G115 -- This conversion is safe until year 2106 |
There was a problem hiding this comment.
Would be nice to use the SDK's TimeFromUnix here instead of fighting the linter.
There was a problem hiding this comment.
I need to get those CodeQL rules going!!
| t.Helper() | ||
| emitterAddr, err := vaa.StringToAddress("0x0290fb167208af455bb137780163b7b7a9a10c16") | ||
| require.NoError(t, err) | ||
| return &common.MessagePublication{ |
There was a problem hiding this comment.
Just for completeness it would be nice to add all of the fields here, i.e. IsReobservation, Unreliable, VerificationState
|
|
||
| // marshaledMsgSizeForBatch builds the submit_observations message for a batch the same way SubmitObservationsToContract does, | ||
| // with worst-case values for the fields other than the observations, and returns its marshaled size. | ||
| func marshaledMsgSizeForBatch(t *testing.T, batch []*common.MessagePublication) int { |
There was a problem hiding this comment.
It might be better to not use int here as the return type
There was a problem hiding this comment.
Vs a smaller type it's practically impossible for us to overflow an int. And based on the logic in the function it will never be negative so I'm not sure why we should go more defensive here?
| } | ||
|
|
||
| // marshaledMsgSizeForBatch builds the submit_observations message for a batch the same way SubmitObservationsToContract does, | ||
| // with worst-case values for the fields other than the observations, and returns its marshaled size. |
There was a problem hiding this comment.
Is this a constant value we could save instead of using a function?
| func packObservationBatches(msgs []*common.MessagePublication, maxBatchCount int, maxMsgSize int) (batches [][]*common.MessagePublication, oversized []*common.MessagePublication) { | ||
| // batchSizes[i] is the size the marshaled observations array for batches[i] would have, including the enclosing brackets. | ||
| var batchSizes []int | ||
| for _, msg := range msgs { |
There was a problem hiding this comment.
Let's add a nil check here
Observation batches are now packed against the exact serialised transaction size rather than always batching up to a maximum batch size of 100, deferring messages that don't fit to a later batch. A message too large to ever fit is submitted in its own batch, with a new metric and associated log.