From 45612177a17c3cc2827551e13c02868fe5c82c95 Mon Sep 17 00:00:00 2001 From: Steven Normore Date: Sun, 20 Jul 2025 20:16:16 -0400 Subject: [PATCH] device/telemetry: lower batch size to be within transaction size limit --- .../internal/telemetry/submitter_test.go | 7 +++- e2e/sdk_telemetry_test.go | 39 +++++++++++++++++++ smartcontract/sdk/go/telemetry/constants.go | 13 ++++--- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/controlplane/telemetry/internal/telemetry/submitter_test.go b/controlplane/telemetry/internal/telemetry/submitter_test.go index 7b00778689..28210904eb 100644 --- a/controlplane/telemetry/internal/telemetry/submitter_test.go +++ b/controlplane/telemetry/internal/telemetry/submitter_test.go @@ -424,8 +424,11 @@ func TestAgentTelemetry_Submitter(t *testing.T) { mu.Lock() defer mu.Unlock() - require.Equal(t, 3, calls, "expected 3 submission calls for 5500 samples with max 2560 per call") - assert.Equal(t, []int{sdktelemetry.MaxSamplesPerBatch, sdktelemetry.MaxSamplesPerBatch, 380}, samplesPerCall, "each call should contain at most 2560 samples") + require.Equal(t, 23, calls, "expected 23 submission calls for 5500 samples with max 245 per call") + for i := range 22 { + assert.Equal(t, sdktelemetry.MaxSamplesPerBatch, samplesPerCall[i]) + } + assert.Equal(t, 110, samplesPerCall[22], "last call should contain 110 samples") }) t.Run("negative_rtts_are_submitted_as_one", func(t *testing.T) { diff --git a/e2e/sdk_telemetry_test.go b/e2e/sdk_telemetry_test.go index 794416352e..d28a7a36e2 100644 --- a/e2e/sdk_telemetry_test.go +++ b/e2e/sdk_telemetry_test.go @@ -266,6 +266,7 @@ func TestE2E_SDK_Telemetry(t *testing.T) { 900000, 1000000, } + t.Run("write second device latency samples", func(t *testing.T) { ctx, cancel := context.WithDeadline(t.Context(), time.Now().Add(30*time.Second)) defer cancel() @@ -309,6 +310,44 @@ func TestE2E_SDK_Telemetry(t *testing.T) { require.Equal(t, uint32(len(combinedSamples)), deviceLatencySamples.NextSampleIndex) require.Equal(t, combinedSamples, deviceLatencySamples.Samples) }) + + t.Run("write largest possible batch of samples per transaction", func(t *testing.T) { + ctx, cancel := context.WithDeadline(t.Context(), time.Now().Add(30*time.Second)) + defer cancel() + start := time.Now() + log.Info("==> Writing largest possible batch of samples per transaction") + sig, res, err := la2AgentTelemetryClient.WriteDeviceLatencySamples(ctx, telemetry.WriteDeviceLatencySamplesInstructionConfig{ + AgentPK: la2DeviceAgentPrivateKey.PublicKey(), + OriginDevicePK: la2DevicePK, + TargetDevicePK: ny5DevicePK, + LinkPK: la2ToNy5LinkPK, + Epoch: epoch, + StartTimestampMicroseconds: secondStartTimestampMicroseconds, + Samples: make([]uint32, telemetry.MaxSamplesPerBatch), + }) + require.NoError(t, err) + for _, msg := range res.Meta.LogMessages { + log.Debug("solana log message", "msg", msg) + } + require.Nil(t, res.Meta.Err, "transaction failed: %+v", res.Meta.Err) + log.Info("==> Wrote largest possible batch of samples per transaction", "sig", sig, "tx", res, "duration", time.Since(start)) + }) + + t.Run("write largest possible batch of samples per transaction +1 (should fail)", func(t *testing.T) { + ctx, cancel := context.WithDeadline(t.Context(), time.Now().Add(30*time.Second)) + defer cancel() + log.Info("==> Writing largest possible batch of samples per transaction +1 (should fail)") + _, _, err := la2AgentTelemetryClient.WriteDeviceLatencySamples(ctx, telemetry.WriteDeviceLatencySamplesInstructionConfig{ + AgentPK: la2DeviceAgentPrivateKey.PublicKey(), + OriginDevicePK: la2DevicePK, + TargetDevicePK: ny5DevicePK, + LinkPK: la2ToNy5LinkPK, + Epoch: epoch, + StartTimestampMicroseconds: secondStartTimestampMicroseconds, + Samples: make([]uint32, telemetry.MaxSamplesPerBatch+1), + }) + require.ErrorIs(t, err, telemetry.ErrSamplesBatchTooLarge) + }) } func airdropAndWait(ctx context.Context, client *solanarpc.Client, pubkey solana.PublicKey, lamports uint64) error { diff --git a/smartcontract/sdk/go/telemetry/constants.go b/smartcontract/sdk/go/telemetry/constants.go index afdecfaeba..bb6334448a 100644 --- a/smartcontract/sdk/go/telemetry/constants.go +++ b/smartcontract/sdk/go/telemetry/constants.go @@ -13,13 +13,14 @@ const ( // when the given PDA does not exist. InstructionErrorAccountDoesNotExist = 1011 - // SolanaMaxPermittedDataIncrease is the maximum number of bytes a program may add to an - // account during a single realloc. - // This is the samples batch size limit in bytes. - SolanaMaxPermittedDataIncrease = 10_240 - // MaxSamplesPerBatch is the maximum number of samples that can be written in a single batch. - MaxSamplesPerBatch = SolanaMaxPermittedDataIncrease / 4 + // + // Messages transmitted to Solana validators must not exceed the IPv6 MTU size to ensure fast + // and reliable network transmission of cluster info over UDP. Solana's networking stack uses a + // conservative MTU size of 1280 bytes which, after accounting for headers, leaves 1232 bytes + // for packet data like serialized transactions. + // https://docs.anza.xyz/proposals/versioned-transactions#problem + MaxSamplesPerBatch = 245 // 980 bytes // MaxSamples is the maximum number of samples that can be written to a single account. MaxSamples = 35_000