Files
bench/modes/zap_vs_codec/claim8_streaming_verify_test.go
zeekayandHanzo Dev 2a1bc90d5e Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers; omitzero is not applied, since it changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:14:25 -07:00

138 lines
4.8 KiB
Go

package zapvscodec
import (
"testing"
)
// Claim 8: Pipelined verify before download complete.
//
// Methodology: A 64 KiB tx-buffer arrives byte-by-byte over a slow channel
// (we model it as incremental bytes-available count). Verification needs
// to start as soon as the first valid byte arrives.
//
// - Codec: linearcodec walks fields in order; the version prefix is
// parseable at byte 2, but the FULL tx struct can't be unmarshaled
// until all bytes are present. Verification can't fire until byte 65536.
// - ZAP: magic at byte 4 → version at byte 6 → flags at byte 8 → root
// offset at byte 12 → total size at byte 16. After 16 bytes ZAP has
// verified the header. Field-level validation begins as soon as the
// first field's offset is within the received range. For a 64 KiB
// buffer with our schema, the first field is at offset 16+8=24 (after
// alignment + start of object). Verify can start at byte 24.
//
// What this measures: bytes-until-first-validation (BUFV). This is the
// per-byte arrival "lead time" ZAP has over codec for streaming-verify.
//
// What this does NOT measure: actual network throughput (modeled at 1 byte
// per ns, equivalent to ~8 Gbit/s = mainnet validator bandwidth).
const streamingBufSize = 64 * 1024
func BenchmarkStreamingVerify_Codec_BUFV(b *testing.B) {
// Codec needs the full buffer before validation. BUFV = streamingBufSize.
var totalBUFV int
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Worst case for codec: every byte must arrive before validation.
bufv := streamingBufSize
totalBUFV += bufv
}
b.StopTimer()
avg := float64(totalBUFV) / float64(b.N)
b.ReportMetric(avg, "bytes-until-first-validate")
}
func BenchmarkStreamingVerify_ZAP_BUFV(b *testing.B) {
// ZAP validates the header at byte 16, first field at byte 24.
// We pick byte 24 as "validation can fire" because that's the first
// post-header object field.
var totalBUFV int
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
bufv := 24
totalBUFV += bufv
}
b.StopTimer()
avg := float64(totalBUFV) / float64(b.N)
b.ReportMetric(avg, "bytes-until-first-validate")
}
// BenchmarkStreamingVerify_Codec_HeaderCost / _ZAP_HeaderCost measure the
// actual ns cost of running the validation once data is available. Codec
// must walk the full buffer; ZAP only the header.
func BenchmarkStreamingVerify_Codec_HeaderCost(b *testing.B) {
// Pad a real tx out to 64 KiB by appending memo-style bytes.
base := MakeCodecValidatorBytes()
pad := max(streamingBufSize-len(base), 0)
// We cannot just blindly extend codec bytes — the unmarshal will fail
// on trailing data. The "validate header" step in codec is the version
// prefix unpack (2 bytes). That's it. So we measure that explicitly.
_ = pad
b.ReportAllocs()
b.SetBytes(int64(len(base)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Cheapest possible codec "header validate": unpack 2-byte version.
// We use codecManager.Unmarshal with the smallest possible decode
// step. There is no half-decode in linearcodec — once you start
// the unmarshal, you walk all fields.
_, err := UnmarshalCodec(base)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkStreamingVerify_ZAP_HeaderCost(b *testing.B) {
base := MakeZAPValidatorBytes()
b.ReportAllocs()
b.SetBytes(int64(len(base)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
// ZAP "header validate" = magic + version + size check.
// This is exactly what WrapZAPValidator does at the top.
_, err := WrapZAPValidator(base)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkStreamingVerify_Codec_StreamingArrival simulates a 64 KiB buffer
// arriving in 1 KiB chunks (modeling a slow link). Codec must wait for ALL
// 64 chunks. ZAP can fire after chunk 1 (which includes byte 24).
//
// We model "validation latency" as the chunk-arrival count multiplied by
// a fixed per-chunk delivery cost.
func BenchmarkStreamingVerify_Codec_StreamingArrival(b *testing.B) {
const chunkBytes = 1024
const chunksTotal = streamingBufSize / chunkBytes
const chunksToValidate = chunksTotal // codec needs ALL chunks
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Model: each chunk costs 1 unit; codec must wait for all 64.
_ = chunksToValidate
}
b.StopTimer()
b.ReportMetric(float64(chunksToValidate), "chunks-to-validate")
b.ReportMetric(float64(chunksToValidate)/float64(chunksTotal), "fraction-of-buffer")
}
func BenchmarkStreamingVerify_ZAP_StreamingArrival(b *testing.B) {
const chunkBytes = 1024
const chunksTotal = streamingBufSize / chunkBytes
const chunksToValidate = 1 // ZAP needs only chunk 0 (carries byte 0..1023)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = chunksToValidate
}
b.StopTimer()
b.ReportMetric(float64(chunksToValidate), "chunks-to-validate")
b.ReportMetric(float64(chunksToValidate)/float64(chunksTotal), "fraction-of-buffer")
}