From 463509195d27c58fb298376decc3a9fcf4f214fd Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Wed, 1 Jul 2026 17:07:08 +0200 Subject: [PATCH 01/35] feat(EN-1334): add usagebuilder subsystem for template + event counters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a dedicated derived-from-audit subsystem that materialises Numscript template usage (count + lastUsed) and per-ledger event counters into a peer secondary Pebble store. Removes the equivalent counters from the FSM authoritative state (EN-1420). Architecture: - New `internal/storage/usagestore/` — dedicated Pebble instance at `/usage/`, own comparer, config, keys, delete-ledger cascade and Pebble metrics. Peer to readstore. - New `internal/application/usagebuilder/` — tails the FSM audit chain (AuditEntry + AuditItem) rather than the log stream, so the raw serialized order (with NumscriptReference) is available. Fetches the produced log via ReadLogBySequence only when the resolved posting count is required (revert txs, script-backed create txs). Constructor injection for signal.Notifications (per feedback_constructor_injection). - FanOut extended to a 4th target so LogCommitted reaches the usagebuilder alongside events / mirror / indexbuilder. Public API: - New `GET /v3/{ledger}/numscripts/{name}/usage` handler + gRPC `GetTemplateUsage` RPC returning `common.TemplateUsage { count, lastUsed }`. - `GetLedgerStats` sources PostingCount / RevertCount / NumscriptExecutionCount / ReferenceCount from the usagestore side-store instead of LedgerBoundaries. Eventually consistent — lags the FSM by up to one usagebuilder tick. FSM cutover (EN-1420): - Increment sites removed from processor_transaction.go, processor_revert_transaction.go, processor_mirror.go. - `referenceUpdates` removed from write_set_counters.updateBoundaryCounters and its caller in write_set.go. - The 4 event-count fields deleted from raft_cmd.proto's LedgerBoundaries message. No `reserved` markers — the field numbers may be reused later since no external contract binds them. Existing Pebble records decode cleanly; the byte slots of the removed fields become unknown-fields and are dropped on unmarshal, which is the intended reset semantics. Operational: - `ledgerctl store rebuild-usage` — drops /usage/ and replays every reachable audit entry from sequence 0. - `metrics.libsonnet` extended with `usage_builder` and `usagestore` namespaces so dashboards can reference the new gauges. - `.golangci.yaml` — usagestore added to the NewWriteSessionFromDB forbidigo exclusion (same rationale as readstore). Docs: - `docs/technical/contributing/api-comparison.md` — new endpoint section and API-comparison table row. - `docs/ops/cli.md` — new `store rebuild-usage` subcommand entry. - `openapi.yml` — new path, `TemplateUsage` schema, `GetTemplateUsageResponse` schema. Related tickets: - EN-1334: per-template usage counters (this PR). - EN-1420: migrate the 4 event-count LedgerBoundaries counters (also this PR — bundled per user request). - EN-1422: attribute-derived counters (volume, metadata, ephemeral, transient) deferred to a dedicated follow-up ticket. Cutover semantics: on production upgrade, the 4 migrated counters restart at 0 and repopulate from the earliest reachable audit entry. Historic pre-upgrade values are lost — accepted trade-off. --- .golangci.yaml | 5 + cmd/ledgerctl/store/command.go | 1 + cmd/ledgerctl/store/rebuild_usage.go | 119 ++ docs/ops/cli.md | 36 + docs/technical/contributing/api-comparison.md | 12 + .../bucket_service_client_generated_test.go | 44 + internal/adapter/grpc/client_bucket.go | 7 + .../adapter/grpc/controller_generated_test.go | 39 + internal/adapter/grpc/server_bucket.go | 19 + .../adapter/http/backend_generated_test.go | 39 + internal/adapter/http/handler.go | 1 + .../http/handlers_get_numscript_usage.go | 36 + internal/application/ctrl/controller.go | 7 + .../application/ctrl/controller_default.go | 52 +- .../ctrl/controller_generated_test.go | 15 + .../ctrl/ctrlmock/controller_generated.go | 15 + internal/application/usagebuilder/builder.go | 277 ++++ .../application/usagebuilder/process_audit.go | 457 ++++++ .../usagebuilder/process_audit_test.go | 120 ++ internal/bootstrap/controller_routed.go | 9 + internal/bootstrap/module.go | 57 +- .../domain/processing/processor_mirror.go | 10 +- .../processor_revert_transaction.go | 6 +- .../processing/processor_transaction.go | 9 +- internal/infra/state/write_set.go | 2 +- internal/infra/state/write_set_counters.go | 11 +- internal/proto/commonpb/common.pb.go | 1454 +++++++++-------- internal/proto/commonpb/common_dethash.pb.go | 11 + internal/proto/commonpb/common_reader.pb.go | 76 + internal/proto/commonpb/common_vtproto.pb.go | 203 +++ internal/proto/raftcmdpb/raft_cmd.pb.go | 61 +- .../proto/raftcmdpb/raft_cmd_reader.pb.go | 20 - .../proto/raftcmdpb/raft_cmd_vtproto.pb.go | 92 -- internal/proto/servicepb/bucket.pb.go | 1240 +++++++------- internal/proto/servicepb/bucket_dethash.pb.go | 11 + internal/proto/servicepb/bucket_grpc.pb.go | 44 + internal/proto/servicepb/bucket_reader.pb.go | 72 + internal/proto/servicepb/bucket_vtproto.pb.go | 220 +++ internal/storage/usagestore/comparer.go | 127 ++ internal/storage/usagestore/delete_ledger.go | 52 + internal/storage/usagestore/keys.go | 64 + internal/storage/usagestore/metrics.go | 66 + internal/storage/usagestore/store.go | 258 +++ internal/storage/usagestore/store_test.go | 155 ++ .../jsonnet/lib/metrics.libsonnet | 16 + misc/proto/bucket.proto | 13 + misc/proto/common.proto | 8 + misc/proto/raft_cmd.proto | 4 - openapi.yml | 70 + 49 files changed, 4266 insertions(+), 1476 deletions(-) create mode 100644 cmd/ledgerctl/store/rebuild_usage.go create mode 100644 internal/adapter/http/handlers_get_numscript_usage.go create mode 100644 internal/application/usagebuilder/builder.go create mode 100644 internal/application/usagebuilder/process_audit.go create mode 100644 internal/application/usagebuilder/process_audit_test.go create mode 100644 internal/storage/usagestore/comparer.go create mode 100644 internal/storage/usagestore/delete_ledger.go create mode 100644 internal/storage/usagestore/keys.go create mode 100644 internal/storage/usagestore/metrics.go create mode 100644 internal/storage/usagestore/store.go create mode 100644 internal/storage/usagestore/store_test.go diff --git a/.golangci.yaml b/.golangci.yaml index 9d1ad11336..ed5e79877a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -163,6 +163,11 @@ linters: # (not OpenWriteSession on the main store). - path: internal/storage/readstore/ linters: [forbidigo] + # The usagestore is a peer secondary store to readstore, dedicated to the + # usagebuilder projections. It manages its own Pebble DB and uses + # NewWriteSessionFromDB by the same rationale. + - path: internal/storage/usagestore/ + linters: [forbidigo] # Tests use OpenWriteSession to seed data; the structural guarantee is on # production paths. - path: _test\.go$ diff --git a/cmd/ledgerctl/store/command.go b/cmd/ledgerctl/store/command.go index 118f67cfd5..308d69801e 100644 --- a/cmd/ledgerctl/store/command.go +++ b/cmd/ledgerctl/store/command.go @@ -19,6 +19,7 @@ func NewCommand() *cobra.Command { cmd.AddCommand(NewBootstrapCommand()) cmd.AddCommand(NewRebuildIndexesCommand()) cmd.AddCommand(NewRebuildAuditIndexCommand()) + cmd.AddCommand(NewRebuildUsageCommand()) cmd.AddCommand(NewCheckpointCommand()) cmd.AddCommand(NewDumpCommand()) cmd.AddCommand(NewBackupCommand()) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go new file mode 100644 index 0000000000..ae9853e6b7 --- /dev/null +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -0,0 +1,119 @@ +package store + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "go.opentelemetry.io/otel/metric/noop" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/cmd/ledgerctl/cmdutil" + "github.com/formancehq/ledger/v3/internal/application/usagebuilder" + "github.com/formancehq/ledger/v3/internal/storage/dal" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +// NewRebuildUsageCommand creates the store rebuild-usage command. +func NewRebuildUsageCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "rebuild-usage", + Short: "Rebuild the usage store from the audit chain (offline)", + Long: `Drop the usage store directory and replay every audit entry from +sequence 0, rebuilding all per-template usage counters and per-ledger event +counters from scratch. This is a purely offline operation — the server must +be stopped. + +Use this after restoring from a backup, when the usage store becomes +corrupted, or when audit-chain history has been extended and you want the +counters to reflect the full history that is still available in Pebble. + +Note that audit entries archived to cold storage are not reconstructed — +the rebuild only replays what is currently reachable in the primary Pebble +store.`, + RunE: runRebuildUsage, + Args: cobra.ExactArgs(0), + ValidArgsFunction: cobra.NoFileCompletions, + } + + cmd.Flags().String("data-dir", "", "Pebble data directory (required)") + cmd.Flags().String("usage-dir", "", "Usage store output directory (default: /usage/)") + cmd.Flags().Int("usage-batch-size", 0, "Number of audit entries per Pebble batch commit (0 = default 200)") + + _ = cmd.MarkFlagRequired("data-dir") + + return cmd +} + +func runRebuildUsage(cmd *cobra.Command, _ []string) error { + var ( + dataDir, _ = cmd.Flags().GetString("data-dir") + usageDir, _ = cmd.Flags().GetString("usage-dir") + batchSize, _ = cmd.Flags().GetInt("usage-batch-size") + ) + + if usageDir == "" { + usageDir = filepath.Join(dataDir, "usage") + } + + logger := logging.NopZap() + + // Drop the existing usage store so the builder starts at cursor=0 and + // no stale counter survives the rebuild. + spinner, _ := pterm.DefaultSpinner.Start("Dropping existing usage store...") + + if err := os.RemoveAll(usageDir); err != nil { + spinner.Fail("Failed to drop usage store directory") + + return cmdutil.Displayed(fmt.Errorf("removing %s: %w", usageDir, err)) + } + + spinner.Success("Usage store dropped at " + usageDir) + + // Open primary Pebble read-only — same as rebuild-indexes. + spinner, _ = pterm.DefaultSpinner.Start("Opening Pebble store (read-only)...") + + pebbleStore, err := dal.OpenReadOnly(dataDir, logger) + if err != nil { + spinner.Fail("Failed to open Pebble store") + + return cmdutil.Displayed(fmt.Errorf("opening Pebble store: %w", err)) + } + + defer func() { _ = pebbleStore.Close() }() + + spinner.Success("Pebble store opened") + + // Create the fresh usage store. + spinner, _ = pterm.DefaultSpinner.Start("Creating usage store...") + + us, err := usagestore.New(usageDir, logger, usagestore.DefaultConfig()) + if err != nil { + spinner.Fail("Failed to create usage store") + + return cmdutil.Displayed(fmt.Errorf("creating usage store: %w", err)) + } + + defer func() { _ = us.Close() }() + + spinner.Success("Usage store created at " + us.Path()) + + // Rebuild — notifications is nil in offline mode (no FSM running). + spinner, _ = pterm.DefaultSpinner.Start("Rebuilding usage projections from the audit chain...") + + builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize) + + lastSeq, err := builder.RebuildAll() + if err != nil { + spinner.Fail("Rebuild failed") + + return cmdutil.Displayed(fmt.Errorf("rebuilding usage projections: %w", err)) + } + + spinner.Success(fmt.Sprintf("Rebuild complete (last audit sequence: %d)", lastSeq)) + + return nil +} diff --git a/docs/ops/cli.md b/docs/ops/cli.md index 92092de02f..74c0986f09 100644 --- a/docs/ops/cli.md +++ b/docs/ops/cli.md @@ -2219,6 +2219,42 @@ ledgerctl store rebuild-audit-index --data-dir ./data --read-index-dir ./custom- --- +### store rebuild-usage + +Drop the usage store directory and replay every audit entry from sequence 0 to rebuild all per-template invocation counters and per-ledger event counters from scratch. Purely offline — the server must be stopped. + +Use this after restoring from a backup, when the usage store becomes corrupted, or when a schema change requires the counters to be re-derived. + +```bash +ledgerctl store rebuild-usage --data-dir /path/to/data [flags] +``` + +**Flags:** + +| Flag | Default | Description | +|------|---------|-------------| +| `--data-dir` | | Pebble data directory (required) | +| `--usage-dir` | | Usage store output directory (default: `/usage/`) | +| `--usage-batch-size` | `0` | Audit entries per Pebble batch commit (`0` = default 200) | + +**Behavior:** + +1. Removes the existing usage store directory (dropping every counter and cursor). +2. Opens the primary Pebble data directory in read-only mode. +3. Creates a fresh usage store. +4. Replays every audit entry reachable in Pebble, materialising per-template usage records and per-ledger event counters. +5. Reports the last processed audit sequence on completion. + +Note that audit entries archived to cold storage are not reconstructed — the rebuild only replays what is currently reachable in the primary Pebble store. Counters end up reflecting invocations from the earliest reachable audit entry forward. + +**Example:** + +```bash +ledgerctl store rebuild-usage --data-dir ./data +``` + +--- + ### audit View the replicated audit log. The audit log captures every proposal (success and failure) that goes through Raft consensus, providing a complete audit trail. diff --git a/docs/technical/contributing/api-comparison.md b/docs/technical/contributing/api-comparison.md index f016e59ea6..a0c26519a1 100644 --- a/docs/technical/contributing/api-comparison.md +++ b/docs/technical/contributing/api-comparison.md @@ -342,6 +342,7 @@ The numscript library allows saving, retrieving, and managing reusable numscript **Endpoints:** - `GET /v3/{ledgerName}/numscripts` - List all saved numscripts for a ledger - `GET /v3/{ledgerName}/numscripts/{name}?version=` - Get a numscript by name (optional version query param) +- `GET /v3/{ledgerName}/numscripts/{name}/usage` - Get invocation count and last-used timestamp for a template - `PUT /v3/{ledgerName}/numscripts/{name}` - Save a numscript (create new version or overwrite latest) - `DELETE /v3/{ledgerName}/numscripts/{name}` - Delete a numscript @@ -364,6 +365,16 @@ When retrieving a numscript via `GET /v3/{ledgerName}/numscripts/{name}`, the `v - `version` (string): Semver version (e.g. `"1.0.0"`) - `createdAt` (string, date-time): Timestamp +**Usage tracking (`GET /v3/{ledgerName}/numscripts/{name}/usage`):** + +Returns per-template invocation counters and the timestamp of the most recent invocation. Populated asynchronously by the `usagebuilder` subsystem, which tails the FSM audit chain and writes to a dedicated secondary Pebble store (`/usage/`). Values are eventually consistent with the FSM and may lag by up to one usagebuilder tick interval (~100 ms). + +A never-invoked template returns a zero-valued response (not 404), so clients handle "never used" uniformly: +- `count` (uint64): Number of times the template has been invoked. `0` means not yet invoked (or the usagebuilder has not caught up). +- `lastUsed` (string, date-time, nullable): Timestamp of the most recent invocation. Absent when count is 0. + +On a fresh ledger the counter builds up organically from cursor=0. On an existing ledger whose audit chain has been partially archived to cold storage, only invocations still present in the primary Pebble store are counted — use `ledgerctl store rebuild-usage` to replay from the reachable start. + ### 10. Prepared Queries and User-Configurable Indexes Prepared queries are reusable, named filter queries stored per-ledger. They can be executed in two modes: `LIST` (returns matching entity IDs with cursor pagination) and `AGGREGATE_VOLUMES` (returns aggregated volumes per asset for matched accounts). @@ -663,6 +674,7 @@ Read endpoints comparison with the original ledger: | `POST /v3/{ledgerName}/prepared-queries/{queryName}/execute` | ✅ | ❌ | Execute a prepared query | | `GET /v3/{ledgerName}/numscripts` | ✅ | ❌ | List all numscripts for a ledger | | `GET /v3/{ledgerName}/numscripts/{name}?version=` | ✅ | ❌ | Get numscript (semver version, empty = latest) | +| `GET /v3/{ledgerName}/numscripts/{name}/usage` | ✅ | ❌ | Get invocation count + last-used timestamp | | `PUT /v3/{ledgerName}/numscripts/{name}` | ✅ | ❌ | Save numscript (semver versioned) | | `DELETE /v3/{ledgerName}/numscripts/{name}` | ✅ | ❌ | Delete numscript | | `GET /v3/{ledgerName}/account-types` | ✅ | ❌ | List account types | diff --git a/internal/adapter/grpc/bucket_service_client_generated_test.go b/internal/adapter/grpc/bucket_service_client_generated_test.go index 1484db28fb..804e0da929 100644 --- a/internal/adapter/grpc/bucket_service_client_generated_test.go +++ b/internal/adapter/grpc/bucket_service_client_generated_test.go @@ -1010,6 +1010,50 @@ func (c *MockBucketServiceClientGetSecondaryMetricsCall) DoAndReturn(f func(cont return c } +// GetTemplateUsage mocks base method. +func (m *MockBucketServiceClient) GetTemplateUsage(ctx context.Context, in *servicepb.GetTemplateUsageRequest, opts ...grpc.CallOption) (*commonpb.TemplateUsage, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetTemplateUsage", varargs...) + ret0, _ := ret[0].(*commonpb.TemplateUsage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateUsage indicates an expected call of GetTemplateUsage. +func (mr *MockBucketServiceClientMockRecorder) GetTemplateUsage(ctx, in any, opts ...any) *MockBucketServiceClientGetTemplateUsageCall { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, in}, opts...) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateUsage", reflect.TypeOf((*MockBucketServiceClient)(nil).GetTemplateUsage), varargs...) + return &MockBucketServiceClientGetTemplateUsageCall{Call: call} +} + +// MockBucketServiceClientGetTemplateUsageCall wrap *gomock.Call +type MockBucketServiceClientGetTemplateUsageCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockBucketServiceClientGetTemplateUsageCall) Return(arg0 *commonpb.TemplateUsage, arg1 error) *MockBucketServiceClientGetTemplateUsageCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockBucketServiceClientGetTemplateUsageCall) Do(f func(context.Context, *servicepb.GetTemplateUsageRequest, ...grpc.CallOption) (*commonpb.TemplateUsage, error)) *MockBucketServiceClientGetTemplateUsageCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockBucketServiceClientGetTemplateUsageCall) DoAndReturn(f func(context.Context, *servicepb.GetTemplateUsageRequest, ...grpc.CallOption) (*commonpb.TemplateUsage, error)) *MockBucketServiceClientGetTemplateUsageCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetTransaction mocks base method. func (m *MockBucketServiceClient) GetTransaction(ctx context.Context, in *servicepb.GetTransactionRequest, opts ...grpc.CallOption) (*servicepb.GetTransactionResponse, error) { m.ctrl.T.Helper() diff --git a/internal/adapter/grpc/client_bucket.go b/internal/adapter/grpc/client_bucket.go index 66b48f9c6f..0c256120fd 100644 --- a/internal/adapter/grpc/client_bucket.go +++ b/internal/adapter/grpc/client_bucket.go @@ -416,6 +416,13 @@ func (g *BucketGrpcClient) GetNumscript(ctx context.Context, ledger, name string }) } +func (g *BucketGrpcClient) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + return g.client.GetTemplateUsage(ctx, &servicepb.GetTemplateUsageRequest{ + Ledger: ledger, + Name: name, + }) +} + func (g *BucketGrpcClient) ListNumscripts(ctx context.Context, ledger string) ([]*commonpb.NumscriptInfo, error) { // Follow x-next-cursor across pages — see ListSigningKeys for the // rationale (this client may wrap a routed leader controller whose diff --git a/internal/adapter/grpc/controller_generated_test.go b/internal/adapter/grpc/controller_generated_test.go index 7127deabce..6e3dc2a447 100644 --- a/internal/adapter/grpc/controller_generated_test.go +++ b/internal/adapter/grpc/controller_generated_test.go @@ -628,6 +628,45 @@ func (c *MockControllerGetNumscriptCall) DoAndReturn(f func(context.Context, str return c } +// GetTemplateUsage mocks base method. +func (m *MockController) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateUsage", ctx, ledger, name) + ret0, _ := ret[0].(*commonpb.TemplateUsage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateUsage indicates an expected call of GetTemplateUsage. +func (mr *MockControllerMockRecorder) GetTemplateUsage(ctx, ledger, name any) *MockControllerGetTemplateUsageCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateUsage", reflect.TypeOf((*MockController)(nil).GetTemplateUsage), ctx, ledger, name) + return &MockControllerGetTemplateUsageCall{Call: call} +} + +// MockControllerGetTemplateUsageCall wrap *gomock.Call +type MockControllerGetTemplateUsageCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockControllerGetTemplateUsageCall) Return(arg0 *commonpb.TemplateUsage, arg1 error) *MockControllerGetTemplateUsageCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockControllerGetTemplateUsageCall) Do(f func(context.Context, string, string) (*commonpb.TemplateUsage, error)) *MockControllerGetTemplateUsageCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockControllerGetTemplateUsageCall) DoAndReturn(f func(context.Context, string, string) (*commonpb.TemplateUsage, error)) *MockControllerGetTemplateUsageCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetTransaction mocks base method. func (m *MockController) GetTransaction(ctx context.Context, ledgerName string, transactionID uint64) (*commonpb.Transaction, *string, error) { m.ctrl.T.Helper() diff --git a/internal/adapter/grpc/server_bucket.go b/internal/adapter/grpc/server_bucket.go index 17dbd497a0..d98f90202f 100644 --- a/internal/adapter/grpc/server_bucket.go +++ b/internal/adapter/grpc/server_bucket.go @@ -1739,6 +1739,25 @@ func (impl *BucketServiceServerImpl) GetNumscript(ctx context.Context, req *serv return c.GetNumscript(ctx, req.GetLedger(), req.GetName(), req.GetVersion()) } +// GetTemplateUsage returns the invocation counter + last-used timestamp for +// a Numscript template. Reads are served from the usagebuilder side-store, +// which is eventually consistent with the FSM — no ReadOptions barrier is +// honored on this endpoint (a min-log-sequence wait wouldn't help, since +// the usagebuilder cursor is decoupled from the log cursor). +func (impl *BucketServiceServerImpl) GetTemplateUsage(ctx context.Context, req *servicepb.GetTemplateUsageRequest) (*commonpb.TemplateUsage, error) { + if _, err := internalauth.Authenticate(ctx, impl.authCfg, internalauth.ScopeQueriesRead); err != nil { + return nil, err + } + + c, cleanup, err := impl.readController(ctx, 0) + if err != nil { + return nil, err + } + defer cleanup() + + return c.GetTemplateUsage(ctx, req.GetLedger(), req.GetName()) +} + func (impl *BucketServiceServerImpl) ListNumscripts(req *servicepb.ListNumscriptsRequest, stream servicepb.BucketService_ListNumscriptsServer) error { ctx, span := bucketTracer.Start(stream.Context(), "grpc.ListNumscripts") defer span.End() diff --git a/internal/adapter/http/backend_generated_test.go b/internal/adapter/http/backend_generated_test.go index 5fd08b566b..e542c4524c 100644 --- a/internal/adapter/http/backend_generated_test.go +++ b/internal/adapter/http/backend_generated_test.go @@ -668,6 +668,45 @@ func (c *MockBackendGetNumscriptCall) DoAndReturn(f func(context.Context, string return c } +// GetTemplateUsage mocks base method. +func (m *MockBackend) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateUsage", ctx, ledger, name) + ret0, _ := ret[0].(*commonpb.TemplateUsage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateUsage indicates an expected call of GetTemplateUsage. +func (mr *MockBackendMockRecorder) GetTemplateUsage(ctx, ledger, name any) *MockBackendGetTemplateUsageCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateUsage", reflect.TypeOf((*MockBackend)(nil).GetTemplateUsage), ctx, ledger, name) + return &MockBackendGetTemplateUsageCall{Call: call} +} + +// MockBackendGetTemplateUsageCall wrap *gomock.Call +type MockBackendGetTemplateUsageCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockBackendGetTemplateUsageCall) Return(arg0 *commonpb.TemplateUsage, arg1 error) *MockBackendGetTemplateUsageCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockBackendGetTemplateUsageCall) Do(f func(context.Context, string, string) (*commonpb.TemplateUsage, error)) *MockBackendGetTemplateUsageCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockBackendGetTemplateUsageCall) DoAndReturn(f func(context.Context, string, string) (*commonpb.TemplateUsage, error)) *MockBackendGetTemplateUsageCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetTransaction mocks base method. func (m *MockBackend) GetTransaction(ctx context.Context, ledgerName string, transactionID uint64) (*commonpb.Transaction, *string, error) { m.ctrl.T.Helper() diff --git a/internal/adapter/http/handler.go b/internal/adapter/http/handler.go index 9ff09c7783..930dd73d61 100644 --- a/internal/adapter/http/handler.go +++ b/internal/adapter/http/handler.go @@ -107,6 +107,7 @@ func NewHandler(logger logging.Logger, backend Backend, authCfg internalauth.Aut r.Get("/{ledgerName}/logs", server.handleListLedgerLogs) r.Get("/{ledgerName}/numscripts", server.handleListNumscripts) r.Get("/{ledgerName}/numscripts/{name}", server.handleGetNumscript) + r.Get("/{ledgerName}/numscripts/{name}/usage", server.handleGetNumscriptUsage) r.Get("/{ledgerName}/indexes/{metadataKey}", server.handleInspectIndex) }) diff --git a/internal/adapter/http/handlers_get_numscript_usage.go b/internal/adapter/http/handlers_get_numscript_usage.go new file mode 100644 index 0000000000..a400ddcb19 --- /dev/null +++ b/internal/adapter/http/handlers_get_numscript_usage.go @@ -0,0 +1,36 @@ +package http + +import ( + "errors" + "net/http" + + "github.com/go-chi/chi/v5" +) + +// handleGetNumscriptUsage handles GET /{ledgerName}/numscripts/{name}/usage. +// Returns the invocation counter + last-used timestamp populated by the +// usagebuilder subsystem. Values are eventually consistent with the FSM +// (may lag by up to one usagebuilder tick). A never-invoked template +// returns a zero-valued response, not a 404 — clients treat 0 uniformly. +func (s *Server) handleGetNumscriptUsage(w http.ResponseWriter, r *http.Request) { + ledgerName, ok := requireLedgerName(w, r) + if !ok { + return + } + + name := chi.URLParam(r, "name") + if name == "" { + writeBadRequest(w, "INVALID_REQUEST", errors.New("numscript name is required")) + + return + } + + usage, err := s.backend.GetTemplateUsage(r.Context(), ledgerName, name) + if err != nil { + handleError(w, r, err) + + return + } + + writeOK(w, usage) +} diff --git a/internal/application/ctrl/controller.go b/internal/application/ctrl/controller.go index f6d89fc28d..37e69888db 100644 --- a/internal/application/ctrl/controller.go +++ b/internal/application/ctrl/controller.go @@ -66,6 +66,13 @@ type Controller interface { GetNumscript(ctx context.Context, ledger, name string, version string) (*commonpb.NumscriptInfo, error) ListNumscripts(ctx context.Context, ledger string) ([]*commonpb.NumscriptInfo, error) + // GetTemplateUsage returns the invocation counter and last-used timestamp + // for a Numscript template. Reads from the usagebuilder side-store, so + // values may lag the live FSM by up to one tick interval. Returns a + // zero-valued TemplateUsage when the template has never been invoked (or + // the usagebuilder has not caught up to any of its invocations yet). + GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) + // Cluster-wide config operations (read-only) GetChapterSchedule(ctx context.Context) (string, error) GetEventsSinks(ctx context.Context) ([]*commonpb.SinkConfig, error) diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index 415cb026c6..1a74c445fd 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -28,6 +28,7 @@ import ( "github.com/formancehq/ledger/v3/internal/query" "github.com/formancehq/ledger/v3/internal/storage/dal" "github.com/formancehq/ledger/v3/internal/storage/readstore" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) const ( @@ -97,6 +98,7 @@ type DefaultController struct { store *dal.Store attrs *attributes.Attributes readStore *readstore.Store + usageStore *usagestore.Store coldReader *coldstorage.ColdReader applyDuration metric.Int64Histogram @@ -109,6 +111,7 @@ func NewDefaultController( logger logging.Logger, attrs *attributes.Attributes, readStore *readstore.Store, + usageStore *usagestore.Store, coldReader *coldstorage.ColdReader, meter metric.Meter, ) *DefaultController { @@ -130,6 +133,7 @@ func NewDefaultController( store: store, attrs: attrs, readStore: readStore, + usageStore: usageStore, coldReader: coldReader, applyDuration: applyDuration, } @@ -498,7 +502,12 @@ func (ctrl *DefaultController) GetAccount(ctx context.Context, ledgerName string } // GetLedgerStats returns aggregate statistics for a ledger. -// All counters are O(1) reads from the LedgerBoundaries attribute. +// TransactionCount, LogCount, VolumeCount, MetadataCount, EphemeralEvictedCount +// and TransientUsedCount come from the LedgerBoundaries attribute. The +// event-count fields (PostingCount, RevertCount, NumscriptExecutionCount, +// ReferenceCount) are derived from the audit chain by the usagebuilder and +// read from the usagestore side-store — expect up to one usagebuilder tick +// interval of lag behind the live FSM (see EN-1420). func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName string) (*commonpb.LedgerStats, error) { handle, err := ctrl.store.NewReadHandle() if err != nil { @@ -528,18 +537,32 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st stats.VolumeCount = boundaries.GetVolumeCount() stats.MetadataCount = boundaries.GetMetadataCount() - stats.ReferenceCount = boundaries.GetReferenceCount() - stats.PostingCount = boundaries.GetPostingCount() stats.EphemeralEvictedCount = boundaries.GetEphemeralEvictedCount() stats.TransientUsedCount = boundaries.GetTransientUsedCount() - stats.RevertCount = boundaries.GetRevertCount() - stats.NumscriptExecutionCount = boundaries.GetNumscriptExecutionCount() if nextLogID := boundaries.GetNextLogId(); nextLogID > 0 { stats.LogCount = nextLogID - 1 } } + // Event-count fields from the usagebuilder side-store. Missing keys read + // as 0 (fresh ledger, or usagebuilder has not caught up yet). + if stats.PostingCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterPosting); err != nil { + return nil, fmt.Errorf("reading posting counter: %w", err) + } + + if stats.RevertCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterRevert); err != nil { + return nil, fmt.Errorf("reading revert counter: %w", err) + } + + if stats.NumscriptExecutionCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterNumscriptExecution); err != nil { + return nil, fmt.Errorf("reading numscript execution counter: %w", err) + } + + if stats.ReferenceCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterReference); err != nil { + return nil, fmt.Errorf("reading reference counter: %w", err) + } + return &stats, nil } @@ -1316,6 +1339,25 @@ func (ctrl *DefaultController) GetNumscript(ctx context.Context, ledger, name st return info, nil } +// GetTemplateUsage returns the invocation counter and last-used timestamp +// for a Numscript template. Values are populated by the usagebuilder +// subsystem asynchronously from the audit chain: a template that was just +// invoked may not yet be reflected here for up to one usagebuilder tick +// (100 ms). Returns a zero-valued TemplateUsage (never nil) when the +// template has never been invoked. +func (ctrl *DefaultController) GetTemplateUsage(_ context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + usage, err := ctrl.usageStore.GetTemplateUsage(ledger, name) + if err != nil { + return nil, fmt.Errorf("reading template usage %q/%q: %w", ledger, name, err) + } + + if usage == nil { + return &commonpb.TemplateUsage{}, nil + } + + return usage, nil +} + // ListNumscripts returns the latest version of all numscripts for a ledger. func (ctrl *DefaultController) ListNumscripts(ctx context.Context, ledger string) ([]*commonpb.NumscriptInfo, error) { handle, err := ctrl.store.NewReadHandle() diff --git a/internal/application/ctrl/controller_generated_test.go b/internal/application/ctrl/controller_generated_test.go index ef58d6f584..f1261e5c9c 100644 --- a/internal/application/ctrl/controller_generated_test.go +++ b/internal/application/ctrl/controller_generated_test.go @@ -268,6 +268,21 @@ func (mr *MockControllerMockRecorder) GetNumscript(ctx, ledger, name, version an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumscript", reflect.TypeOf((*MockController)(nil).GetNumscript), ctx, ledger, name, version) } +// GetTemplateUsage mocks base method. +func (m *MockController) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateUsage", ctx, ledger, name) + ret0, _ := ret[0].(*commonpb.TemplateUsage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateUsage indicates an expected call of GetTemplateUsage. +func (mr *MockControllerMockRecorder) GetTemplateUsage(ctx, ledger, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateUsage", reflect.TypeOf((*MockController)(nil).GetTemplateUsage), ctx, ledger, name) +} + // GetTransaction mocks base method. func (m *MockController) GetTransaction(ctx context.Context, ledgerName string, transactionID uint64) (*commonpb.Transaction, *string, error) { m.ctrl.T.Helper() diff --git a/internal/application/ctrl/ctrlmock/controller_generated.go b/internal/application/ctrl/ctrlmock/controller_generated.go index d04f872005..63a281d9e8 100644 --- a/internal/application/ctrl/ctrlmock/controller_generated.go +++ b/internal/application/ctrl/ctrlmock/controller_generated.go @@ -268,6 +268,21 @@ func (mr *MockControllerMockRecorder) GetNumscript(ctx, ledger, name, version an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumscript", reflect.TypeOf((*MockController)(nil).GetNumscript), ctx, ledger, name, version) } +// GetTemplateUsage mocks base method. +func (m *MockController) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateUsage", ctx, ledger, name) + ret0, _ := ret[0].(*commonpb.TemplateUsage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTemplateUsage indicates an expected call of GetTemplateUsage. +func (mr *MockControllerMockRecorder) GetTemplateUsage(ctx, ledger, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateUsage", reflect.TypeOf((*MockController)(nil).GetTemplateUsage), ctx, ledger, name) +} + // GetTransaction mocks base method. func (m *MockController) GetTransaction(ctx context.Context, ledgerName string, transactionID uint64) (*commonpb.Transaction, *string, error) { m.ctrl.T.Helper() diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go new file mode 100644 index 0000000000..632f2b69f2 --- /dev/null +++ b/internal/application/usagebuilder/builder.go @@ -0,0 +1,277 @@ +// Package usagebuilder tails the FSM audit chain and materialises the usage +// projections consumed by the housekeeping API: per-Numscript-template +// invocation counters (count + lastUsed) and per-ledger event counters +// (postings, reverts, numscript executions, references). +// +// The projection is not part of the FSM authoritative state — it lives in a +// dedicated secondary Pebble instance (usagestore) and is rebuildable from +// cursor=0 on demand. +// +// The subsystem reads from the audit chain (AuditEntry + AuditItem in +// ZoneCold) rather than the log stream because the audit item carries the +// raw serialized order — the only place where the Numscript reference +// survives past apply (the log's CreatedTransaction payload does not). +// For posting/revert counts, we fetch the specific log referenced by +// AuditItem.LogSequence — a single Get on the hot Pebble cache. +// +// Runs on every node; each replica maintains its own cursor (last consumed +// audit sequence). Eventually consistent with the FSM: reads may lag by up +// to one tick interval (100 ms) plus batch drain time. +package usagebuilder + +import ( + "context" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel/metric" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/internal/pkg/signal" + "github.com/formancehq/ledger/v3/internal/pkg/worker" + "github.com/formancehq/ledger/v3/internal/query" + "github.com/formancehq/ledger/v3/internal/storage/dal" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +// DefaultBatchSize is the default number of audit entries per Pebble batch commit. +const DefaultBatchSize = 200 + +// Builder tails the FSM audit chain and populates the usagestore projections. +// Runs as a background goroutine on all nodes (not leader-only). Progress is +// stored in the usagestore itself under [0xFE][0x01]. +type Builder struct { + pebbleStore *dal.Store + usageStore *usagestore.Store + notifications *signal.Notifications + logger logging.Logger + meter metric.Meter + w worker.Worker + + batchSize int + + lastProcessedAuditSeq atomic.Uint64 + pebbleLastAuditSeq atomic.Uint64 + entriesProcessed atomic.Uint64 + metricsRegistration metric.Registration +} + +// NewBuilder wires the usagebuilder subsystem. Notifications is injected via +// constructor rather than a setter to keep the fx graph explicit — see +// feedback_constructor_injection in the project memory. +func NewBuilder( + pebbleStore *dal.Store, + usageStore *usagestore.Store, + notifications *signal.Notifications, + logger logging.Logger, + meter metric.Meter, + batchSize int, +) *Builder { + if batchSize <= 0 { + batchSize = DefaultBatchSize + } + + return &Builder{ + pebbleStore: pebbleStore, + usageStore: usageStore, + notifications: notifications, + logger: logger.WithFields(map[string]any{"cmp": "usage-builder"}), + meter: meter, + batchSize: batchSize, + } +} + +// Start begins the background loop and registers OTEL metrics. +func (b *Builder) Start() { + if reg, err := b.registerMetrics(); err == nil { + b.metricsRegistration = reg + } + + b.w = worker.New() + b.w.RunCtx(b.loop) +} + +// Stop gracefully stops the background loop and unregisters OTEL metrics. +func (b *Builder) Stop() { + b.w.Stop() + + if b.metricsRegistration != nil { + _ = b.metricsRegistration.Unregister() + } +} + +// LastProcessedAuditSequence returns the last audit sequence consumed (from +// the atomic cache — same value as usagestore.ReadProgress but without a +// Pebble Get). +func (b *Builder) LastProcessedAuditSequence() uint64 { + return b.lastProcessedAuditSeq.Load() +} + +// PebbleLastAuditSequence returns the last known Pebble audit sequence (from +// the atomic cache). +func (b *Builder) PebbleLastAuditSequence() uint64 { + return b.pebbleLastAuditSeq.Load() +} + +// registerMetrics registers observable gauges for the usagebuilder. +func (b *Builder) registerMetrics() (metric.Registration, error) { + lastProcessedGauge, err := b.meter.Int64ObservableGauge( + "usage.builder.last_processed_audit_sequence", + metric.WithDescription("Last audit sequence consumed by the usagebuilder"), + ) + if err != nil { + return nil, err + } + + pebbleLastGauge, err := b.meter.Int64ObservableGauge( + "usage.builder.pebble_last_audit_sequence", + metric.WithDescription("Last audit sequence in Pebble"), + ) + if err != nil { + return nil, err + } + + lagGauge, err := b.meter.Int64ObservableGauge( + "usage.builder.lag", + metric.WithDescription("Number of audit entries the usagebuilder is behind Pebble"), + ) + if err != nil { + return nil, err + } + + entriesProcessedGauge, err := b.meter.Int64ObservableGauge( + "usage.builder.entries_processed_total", + metric.WithDescription("Total number of audit entries consumed since process start"), + ) + if err != nil { + return nil, err + } + + return b.meter.RegisterCallback( + func(_ context.Context, o metric.Observer) error { + processed := int64(b.lastProcessedAuditSeq.Load()) + pebbleLast := int64(b.pebbleLastAuditSeq.Load()) + + lag := max(pebbleLast-processed, 0) + + o.ObserveInt64(lastProcessedGauge, processed) + o.ObserveInt64(pebbleLastGauge, pebbleLast) + o.ObserveInt64(lagGauge, lag) + o.ObserveInt64(entriesProcessedGauge, int64(b.entriesProcessed.Load())) + + return nil + }, + lastProcessedGauge, + pebbleLastGauge, + lagGauge, + entriesProcessedGauge, + ) +} + +// loop is the main goroutine driven by Start(). Reads cursor from the usage +// store, catches up on any pending audit entries, then tails via a 100 ms +// ticker plus the LogCommitted notification (fires whenever the FSM +// advances, which is also when the audit chain advances). +func (b *Builder) loop(ctx context.Context) { + cursor, err := b.usageStore.ReadProgress() + if err != nil { + b.logger.Errorf("Failed to read usage progress: %v", err) + + return + } + + b.lastProcessedAuditSeq.Store(cursor) + + // Seed pebble last audit sequence. Handle closed immediately to release + // the RLock — keeping it open would deadlock with RestoreCheckpoint + // (write lock) when processAuditEntries takes a new RLock. + var pebbleLast uint64 + if handle, err := b.pebbleStore.NewDirectReadHandle(); err != nil { + b.logger.Errorf("Failed to create read handle: %v", err) + + return + } else { + if v, err := query.ReadLastAuditSequence(handle); err == nil { + pebbleLast = v + b.pebbleLastAuditSeq.Store(v) + } + + _ = handle.Close() + } + + b.logger.WithFields(map[string]any{ + "cursor": cursor, + "pebbleLast": pebbleLast, + "gap": int64(pebbleLast) - int64(cursor), + }).Infof("Usage builder started") + + // Initial catch-up: time-bounded iterations to release the Pebble + // snapshot between passes (same rationale as indexbuilder catchUpBudget). + const catchUpBudget = 5 * time.Second + + prevCursor := cursor + savedBatchSize := b.batchSize + b.batchSize = max(b.batchSize, 2_000) + + for { + select { + case <-ctx.Done(): + b.batchSize = savedBatchSize + + return + default: + } + + before := cursor + deadline := time.Now().Add(catchUpBudget) + + if cursor, err = b.processAuditEntries(ctx, cursor, deadline); err != nil { + b.logger.Errorf("Error during initial catch-up: %v", err) + + break + } + + if cursor == before { + break + } + } + + b.batchSize = savedBatchSize + + if cursor > prevCursor { + b.logger.WithFields(map[string]any{ + "from": prevCursor, + "to": cursor, + "entries": cursor - prevCursor, + }).Infof("Initial catch-up complete") + } + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + b.logger.Infof("Usage builder stopped") + + return + case <-b.notifications.LogCommitted.C(): + case <-ticker.C: + } + + // Fast path: skip Pebble iterator when nothing new has landed. The + // LastSequence atomic tracks the last LOG sequence — a strict lower + // bound on the last audit sequence (audit entries are written in + // the same batch as their logs). Comparing against our audit cursor + // is conservative: we may wake up spuriously if the last batch had + // only audit updates (rare), but we never miss real work. + if cached := b.notifications.LastSequence.Load(); cached != 0 && cached <= cursor { + continue + } + + if cursor, err = b.processAuditEntries(ctx, cursor, time.Time{}); err != nil { + b.logger.Errorf("Error processing audit entries: %v", err) + } + } +} diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go new file mode 100644 index 0000000000..914bb56ae9 --- /dev/null +++ b/internal/application/usagebuilder/process_audit.go @@ -0,0 +1,457 @@ +package usagebuilder + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + "google.golang.org/protobuf/proto" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/proto/raftcmdpb" + "github.com/formancehq/ledger/v3/internal/query" + "github.com/formancehq/ledger/v3/internal/storage/dal" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +// templateKey identifies a per-ledger, per-template aggregation slot. +type templateKey struct { + ledger string + template string +} + +// templateDelta accumulates a per-batch increment for one template. +type templateDelta struct { + count uint64 + lastUsed *commonpb.Timestamp // most recent timestamp seen this batch +} + +// counterDelta is a signed delta for a per-ledger event counter. Deltas are +// always non-negative today (all counters are monotonically increasing) but +// int64 leaves room for future decrement paths (e.g. a rollback log type). +type counterDelta = int64 + +// batchState holds the in-flight aggregation for one batch: per-ledger +// counter deltas + per-template usage deltas. Reset by newBatchState. +type batchState struct { + counters map[string]map[byte]counterDelta + templates map[templateKey]templateDelta +} + +func newBatchState() *batchState { + return &batchState{ + counters: make(map[string]map[byte]counterDelta), + templates: make(map[templateKey]templateDelta), + } +} + +// addCounter accumulates a delta on the (ledger, counterID) slot. +func (s *batchState) addCounter(ledger string, counterID byte, delta counterDelta) { + inner, ok := s.counters[ledger] + if !ok { + inner = make(map[byte]counterDelta, 4) + s.counters[ledger] = inner + } + + inner[counterID] += delta +} + +// addTemplateUsage bumps the template usage aggregation. When multiple +// invocations of the same template land in one batch the max timestamp wins +// (matches the "lastUsed = most recent invocation" semantics). +func (s *batchState) addTemplateUsage(ledger, template string, ts *commonpb.Timestamp) { + k := templateKey{ledger: ledger, template: template} + cur := s.templates[k] + cur.count++ + + if ts != nil && (cur.lastUsed == nil || timestampGreater(ts, cur.lastUsed)) { + cur.lastUsed = ts + } + + s.templates[k] = cur +} + +// timestampGreater reports whether a > b in wall-clock ordering. Both +// operands are non-nil. commonpb.Timestamp encodes nanoseconds-since-epoch +// as a single uint64 field (data), so ordering is direct integer compare. +func timestampGreater(a, b *commonpb.Timestamp) bool { + return a.GetData() > b.GetData() +} + +// empty reports whether the batch has no writes queued. +func (s *batchState) empty() bool { + return len(s.counters) == 0 && len(s.templates) == 0 +} + +// RebuildAll replays every audit entry from sequence 0, materialising the +// usagestore projections from scratch. Intended for offline use via +// `ledgerctl store rebuild-usage`. Returns the last processed audit sequence. +func (b *Builder) RebuildAll() (uint64, error) { + return b.processAuditEntries(context.Background(), 0, time.Time{}) +} + +// processAuditEntries iterates audit entries after cursor, dispatches each +// item, and commits per-ledger counter deltas + template usage updates +// atomically alongside the cursor advance. Returns the new cursor. +// +// When deadline is non-zero, processing stops once the deadline has passed +// so the caller (initial catch-up loop) can release the Pebble snapshot +// between iterations. +func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadline time.Time) (uint64, error) { + handle, err := b.pebbleStore.NewDirectReadHandle() + if err != nil { + return cursor, fmt.Errorf("creating read handle for audit processing: %w", err) + } + + defer func() { _ = handle.Close() }() + + // afterSequence is passed by pointer to opt into the ">= cursor+1" filter; + // nil would stream from the very first entry. + afterSeq := cursor + + entriesCursor, err := query.ReadAuditEntries(ctx, handle, &afterSeq) + if err != nil { + return cursor, fmt.Errorf("opening audit cursor: %w", err) + } + + defer func() { _ = entriesCursor.Close() }() + + startCursor := cursor + lastProgressLog := time.Now() + + for { + select { + case <-ctx.Done(): + return cursor, ctx.Err() + default: + } + + state := newBatchState() + + var ( + batchCount int + lastAuditSeq uint64 + eof bool + ) + + for batchCount < b.batchSize { + entry, err := entriesCursor.Next() + if err != nil { + if errors.Is(err, io.EOF) { + eof = true + + break + } + + return cursor, fmt.Errorf("reading audit entry: %w", err) + } + + lastAuditSeq = entry.GetSequence() + batchCount++ + + // Failed proposals: no state change, nothing to project. The + // hash chain still binds them (compareIdempotencyOutcomes) + // but the usagebuilder is only interested in state deltas. + if entry.GetSuccess() == nil { + continue + } + + items, err := query.ReadAuditItems(ctx, handle, entry.GetSequence()) + if err != nil { + return cursor, fmt.Errorf("reading audit items for seq %d: %w", entry.GetSequence(), err) + } + + for _, item := range items { + // LogSequence == 0 → idempotent replay or non-log-producing + // order (metadata schema changes, etc.). Skip: no work. + if item.GetLogSequence() == 0 { + continue + } + + order := &raftcmdpb.Order{} + if err := proto.Unmarshal(item.GetSerializedOrder(), order); err != nil { + return cursor, fmt.Errorf("unmarshaling audit item order (audit_seq=%d, idx=%d): %w", + entry.GetSequence(), item.GetOrderIndex(), err) + } + + if err := b.dispatchOrder(ctx, handle, order, item.GetLogSequence(), state); err != nil { + return cursor, err + } + } + } + + if batchCount == 0 { + break + } + + if err := b.commitBatch(state, lastAuditSeq); err != nil { + return cursor, err + } + + cursor = lastAuditSeq + b.lastProcessedAuditSeq.Store(cursor) + b.entriesProcessed.Add(uint64(batchCount)) + + // Sample Pebble last audit sequence from the FSM notification cache + // when available. Notifications is nil in offline rebuild + // (`ledgerctl store rebuild-usage`) where no FSM is running — the + // atomic is skipped in that path. + if b.notifications != nil { + if cached := b.notifications.LastSequence.Load(); cached > 0 { + b.pebbleLastAuditSeq.Store(cached) + } + } + + // Periodic progress logging for long catch-up runs. + if now := time.Now(); now.Sub(lastProgressLog) >= 10*time.Second { + b.logger.WithFields(map[string]any{ + "cursor": cursor, + "from": startCursor, + "processed": cursor - startCursor, + }).Infof("processAuditEntries progress") + + lastProgressLog = now + } + + if eof { + break + } + + if !deadline.IsZero() && time.Now().After(deadline) { + break + } + } + + return cursor, nil +} + +// dispatchOrder inspects a raw Order and accumulates counter / template +// deltas into the batch state. Fetches the produced log when the resolved +// posting count is required (revert txs, script-backed create txs). +func (b *Builder) dispatchOrder( + ctx context.Context, + handle dal.PebbleGetter, + order *raftcmdpb.Order, + logSeq uint64, + state *batchState, +) error { + scoped := order.GetLedgerScoped() + if scoped == nil { + // System-scoped orders (cluster config, chapter close, …) do not + // contribute to per-ledger usage counters. Skip. + return nil + } + + ledger := scoped.GetLedger() + if ledger == "" { + return nil + } + + apply := scoped.GetApply() + if apply == nil { + // Non-apply orders (create/delete/promote/mirror-ingest) are not + // event-counted in this MVP. + return nil + } + + switch data := apply.GetData().(type) { + case *raftcmdpb.LedgerApplyOrder_CreateTransaction: + return b.dispatchCreateTransaction(ctx, handle, ledger, data.CreateTransaction, logSeq, state) + case *raftcmdpb.LedgerApplyOrder_RevertTransaction: + return b.dispatchRevertTransaction(ctx, handle, ledger, logSeq, state) + } + + return nil +} + +// dispatchCreateTransaction increments posting, reference, numscript-exec, +// and template usage counters for a create-tx order. +func (b *Builder) dispatchCreateTransaction( + ctx context.Context, + handle dal.PebbleGetter, + ledger string, + order *raftcmdpb.CreateTransactionOrder, + logSeq uint64, + state *batchState, +) error { + // Resolved posting count lives on the log — the order carries raw + // postings only for the non-scripted path. + postings, err := b.postingsFromLog(ctx, handle, logSeq) + if err != nil { + return err + } + + if postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(postings)) + } + + if order.GetReference() != "" { + state.addCounter(ledger, usagestore.CounterReference, 1) + } + + isScripted := order.GetNumscriptReference() != nil || + (order.GetScript() != nil && order.GetScript().GetPlain() != "") + + if isScripted { + state.addCounter(ledger, usagestore.CounterNumscriptExecution, 1) + } + + if ref := order.GetNumscriptReference(); ref != nil { + state.addTemplateUsage(ledger, ref.GetName(), order.GetTimestamp()) + } + + return nil +} + +// dispatchRevertTransaction increments revert and posting counters for a +// revert-tx order. The resolved reverse-postings live on the log. +func (b *Builder) dispatchRevertTransaction( + ctx context.Context, + handle dal.PebbleGetter, + ledger string, + logSeq uint64, + state *batchState, +) error { + state.addCounter(ledger, usagestore.CounterRevert, 1) + + postings, err := b.postingsFromLog(ctx, handle, logSeq) + if err != nil { + return err + } + + if postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(postings)) + } + + return nil +} + +// postingsFromLog fetches the log at logSeq and returns the resolved posting +// count. Handles both CreatedTransaction and RevertedTransaction payloads. +// Returns 0 if the log does not exist or carries no transaction (e.g. +// metadata-only logs). +func (b *Builder) postingsFromLog(ctx context.Context, handle dal.PebbleGetter, logSeq uint64) (int, error) { + log, err := query.ReadLogBySequence(ctx, handle, logSeq) + if err != nil { + return 0, fmt.Errorf("reading log at seq %d: %w", logSeq, err) + } + + if log == nil { + return 0, nil + } + + apply, ok := log.GetPayload().GetType().(*commonpb.LogPayload_Apply) + if !ok || apply.Apply == nil { + return 0, nil + } + + ledgerLog := apply.Apply.GetLog() + if ledgerLog == nil || ledgerLog.GetData() == nil { + return 0, nil + } + + switch p := ledgerLog.GetData().GetPayload().(type) { + case *commonpb.LedgerLogPayload_CreatedTransaction: + return len(p.CreatedTransaction.GetTransaction().GetPostings()), nil + case *commonpb.LedgerLogPayload_RevertedTransaction: + return len(p.RevertedTransaction.GetRevertTransaction().GetPostings()), nil + } + + return 0, nil +} + +// commitBatch applies the accumulated counter / template deltas to the +// usagestore and advances the cursor — all in a single Pebble batch commit. +func (b *Builder) commitBatch(state *batchState, cursor uint64) error { + batch := b.usageStore.NewBatch() + + // Counter deltas: read-modify-write against the usagestore. Not the + // FSM's Pebble — invariant #3 does not apply here. + for ledger, counters := range state.counters { + for counterID, delta := range counters { + current, err := b.usageStore.GetCounter(ledger, counterID) + if err != nil { + _ = batch.Cancel() + + return fmt.Errorf("reading counter %#x for ledger %q: %w", counterID, ledger, err) + } + + next := applyDelta(current, delta) + + if err := b.usageStore.PutCounter(batch, ledger, counterID, next); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("writing counter %#x for ledger %q: %w", counterID, ledger, err) + } + } + } + + // Template deltas: same read-modify-write pattern on the TemplateUsage + // proto. count is additive; last_used is max(previous, batch max). + for k, delta := range state.templates { + current, err := b.usageStore.GetTemplateUsage(k.ledger, k.template) + if err != nil { + _ = batch.Cancel() + + return fmt.Errorf("reading template usage %q/%q: %w", k.ledger, k.template, err) + } + + next := mergeTemplateUsage(current, delta) + + if err := b.usageStore.PutTemplateUsage(batch, k.ledger, k.template, next); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("writing template usage %q/%q: %w", k.ledger, k.template, err) + } + } + + if err := b.usageStore.WriteProgress(batch, cursor); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("writing usage progress: %w", err) + } + + if err := batch.Commit(); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("committing usage batch: %w", err) + } + + return nil +} + +// applyDelta safely adds a signed delta to a uint64 counter, clamping at +// zero on underflow. Same helper as write_set_counters.applyDelta but kept +// local so the usagebuilder doesn't pull in state. +func applyDelta(current uint64, delta counterDelta) uint64 { + if delta >= 0 { + return current + uint64(delta) + } + + sub := uint64(-delta) + if sub > current { + return 0 + } + + return current - sub +} + +// mergeTemplateUsage folds a per-batch delta into the persisted TemplateUsage. +// A nil `current` (no persisted entry yet) is treated as {count: 0, lastUsed: nil}. +func mergeTemplateUsage(current *commonpb.TemplateUsage, delta templateDelta) *commonpb.TemplateUsage { + next := &commonpb.TemplateUsage{} + if current != nil { + next.Count = current.GetCount() + delta.count + next.LastUsed = current.GetLastUsed() + } else { + next.Count = delta.count + } + + if delta.lastUsed != nil && (next.GetLastUsed() == nil || timestampGreater(delta.lastUsed, next.GetLastUsed())) { + next.LastUsed = delta.lastUsed + } + + return next +} diff --git a/internal/application/usagebuilder/process_audit_test.go b/internal/application/usagebuilder/process_audit_test.go new file mode 100644 index 0000000000..a749464b8b --- /dev/null +++ b/internal/application/usagebuilder/process_audit_test.go @@ -0,0 +1,120 @@ +package usagebuilder + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +func TestBatchState_AddCounterAggregates(t *testing.T) { + t.Parallel() + + s := newBatchState() + + s.addCounter("l1", usagestore.CounterPosting, 3) + s.addCounter("l1", usagestore.CounterPosting, 5) + s.addCounter("l1", usagestore.CounterRevert, 2) + s.addCounter("l2", usagestore.CounterPosting, 7) + + assert.Equal(t, counterDelta(8), s.counters["l1"][usagestore.CounterPosting]) + assert.Equal(t, counterDelta(2), s.counters["l1"][usagestore.CounterRevert]) + assert.Equal(t, counterDelta(7), s.counters["l2"][usagestore.CounterPosting]) +} + +func TestBatchState_AddTemplateUsageMaxLastUsed(t *testing.T) { + t.Parallel() + + s := newBatchState() + + early := &commonpb.Timestamp{Data: 100} + mid := &commonpb.Timestamp{Data: 500} + late := &commonpb.Timestamp{Data: 900} + + // Ordering shouldn't matter — the max of the three timestamps wins. + s.addTemplateUsage("l1", "payout", mid) + s.addTemplateUsage("l1", "payout", late) + s.addTemplateUsage("l1", "payout", early) + + got := s.templates[templateKey{ledger: "l1", template: "payout"}] + assert.Equal(t, uint64(3), got.count) + assert.Equal(t, late, got.lastUsed) +} + +func TestBatchState_AddTemplateUsageNilTimestamp(t *testing.T) { + t.Parallel() + + s := newBatchState() + + // A nil timestamp bumps the counter but must not overwrite an already-seen + // non-nil lastUsed. + s.addTemplateUsage("l1", "payout", &commonpb.Timestamp{Data: 500}) + s.addTemplateUsage("l1", "payout", nil) + + got := s.templates[templateKey{ledger: "l1", template: "payout"}] + assert.Equal(t, uint64(2), got.count) + assert.Equal(t, uint64(500), got.lastUsed.GetData()) +} + +func TestBatchState_EmptyOnFreshState(t *testing.T) { + t.Parallel() + + assert.True(t, newBatchState().empty()) +} + +func TestApplyDelta(t *testing.T) { + t.Parallel() + + assert.Equal(t, uint64(15), applyDelta(10, 5)) + assert.Equal(t, uint64(5), applyDelta(10, -5)) + assert.Equal(t, uint64(0), applyDelta(10, -10)) + assert.Equal(t, uint64(0), applyDelta(3, -10), "underflow must clamp at zero") + assert.Equal(t, uint64(7), applyDelta(7, 0)) +} + +func TestTimestampGreater(t *testing.T) { + t.Parallel() + + a := &commonpb.Timestamp{Data: 100} + b := &commonpb.Timestamp{Data: 200} + + assert.True(t, timestampGreater(b, a)) + assert.False(t, timestampGreater(a, b)) + assert.False(t, timestampGreater(a, a), "equal timestamps are not greater") +} + +func TestMergeTemplateUsage_NilCurrent(t *testing.T) { + t.Parallel() + + ts := &commonpb.Timestamp{Data: 500} + + got := mergeTemplateUsage(nil, templateDelta{count: 3, lastUsed: ts}) + assert.Equal(t, uint64(3), got.GetCount()) + assert.Equal(t, ts, got.GetLastUsed()) +} + +func TestMergeTemplateUsage_WithCurrent(t *testing.T) { + t.Parallel() + + current := &commonpb.TemplateUsage{ + Count: 10, + LastUsed: &commonpb.Timestamp{Data: 500}, + } + + // Newer batch timestamp wins. + got := mergeTemplateUsage(current, templateDelta{count: 5, lastUsed: &commonpb.Timestamp{Data: 900}}) + assert.Equal(t, uint64(15), got.GetCount()) + assert.Equal(t, uint64(900), got.GetLastUsed().GetData()) + + // Older batch timestamp is ignored. + got = mergeTemplateUsage(current, templateDelta{count: 2, lastUsed: &commonpb.Timestamp{Data: 200}}) + assert.Equal(t, uint64(12), got.GetCount()) + assert.Equal(t, uint64(500), got.GetLastUsed().GetData(), "current lastUsed is later — keep it") + + // Nil batch timestamp: current lastUsed preserved. + got = mergeTemplateUsage(current, templateDelta{count: 1, lastUsed: nil}) + assert.Equal(t, uint64(11), got.GetCount()) + assert.Equal(t, uint64(500), got.GetLastUsed().GetData()) +} diff --git a/internal/bootstrap/controller_routed.go b/internal/bootstrap/controller_routed.go index 4cdf4b8da3..f19d323654 100644 --- a/internal/bootstrap/controller_routed.go +++ b/internal/bootstrap/controller_routed.go @@ -361,6 +361,15 @@ func (b *RoutedController) ListNumscripts(ctx context.Context, ledger string) ([ return c.ListNumscripts(ctx, ledger) } +func (b *RoutedController) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + c, _, err := b.readCtrl(ctx) + if err != nil { + return nil, err + } + + return c.GetTemplateUsage(ctx, ledger, name) +} + func (b *RoutedController) GetChapterSchedule(ctx context.Context) (string, error) { c, _, err := b.readCtrl(ctx) if err != nil { diff --git a/internal/bootstrap/module.go b/internal/bootstrap/module.go index 8ca01ba035..22e642b0b3 100644 --- a/internal/bootstrap/module.go +++ b/internal/bootstrap/module.go @@ -40,6 +40,7 @@ import ( "github.com/formancehq/ledger/v3/internal/application/indexbuilder" "github.com/formancehq/ledger/v3/internal/application/membership" "github.com/formancehq/ledger/v3/internal/application/mirror" + "github.com/formancehq/ledger/v3/internal/application/usagebuilder" "github.com/formancehq/ledger/v3/internal/domain/crypto/keystore" "github.com/formancehq/ledger/v3/internal/domain/crypto/signing" "github.com/formancehq/ledger/v3/internal/domain/processing/numscript" @@ -71,6 +72,7 @@ import ( "github.com/formancehq/ledger/v3/internal/storage/dal" "github.com/formancehq/ledger/v3/internal/storage/readstore" "github.com/formancehq/ledger/v3/internal/storage/spool" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" "github.com/formancehq/ledger/v3/internal/storage/wal" ) @@ -394,6 +396,7 @@ func Module() fx.Option { eventNotifications *signal.Notifications, mirrorNotifications *signal.Notifications, indexNotifications *signal.Notifications, + usageNotifications *signal.Notifications, bloomFilters *bloom.FilterSet, membership *raftmembership.Membership, ) (*state.Machine, error) { @@ -402,8 +405,8 @@ func Module() fx.Option { idempotencyTTLMicros := uint64(cfg.IdempotencyTTL.Microseconds()) // Fan-out: Machine emits to a single Notifier; FanOut dispatches - // to the per-consumer Notifications (events, mirror, index). - fanOut := signal.NewFanOut(eventNotifications, mirrorNotifications, indexNotifications) + // to the per-consumer Notifications (events, mirror, index, usage). + fanOut := signal.NewFanOut(eventNotifications, mirrorNotifications, indexNotifications, usageNotifications) // Sub-objects built in-line so NewMachine receives them pre-built. registry := state.NewStateRegistry(c, attrs, idempotencyTTLMicros) @@ -433,7 +436,7 @@ func Module() fx.Option { }).Infof("FSM Machine created") return m, nil - }, fx.ParamTags(``, ``, ``, ``, ``, ``, ``, ``, `name:"events"`, `name:"mirror"`, `name:"index"`, ``, ``)), + }, fx.ParamTags(``, ``, ``, ``, ``, ``, ``, ``, `name:"events"`, `name:"mirror"`, `name:"index"`, `name:"usage"`, ``, ``)), func( params struct { fx.In @@ -632,6 +635,7 @@ func Module() fx.Option { fx.Annotate(events.NewManager, fx.ParamTags(``, ``, ``, ``, ``, `name:"events"`)), fx.Annotate(signal.NewNotifications, fx.ResultTags(`name:"mirror"`)), fx.Annotate(signal.NewNotifications, fx.ResultTags(`name:"index"`)), + fx.Annotate(signal.NewNotifications, fx.ResultTags(`name:"usage"`)), fx.Annotate(func(store *dal.Store, proposer mirror.Proposer, builder *plan.Builder, logger logging.Logger, notifications *signal.Notifications, meterProvider metric.MeterProvider, cfg Config) *mirror.Manager { return mirror.NewManager(store, proposer, builder, logger, notifications, meterProvider, cfg.MirrorMaxBatchSize) }, fx.ParamTags(``, ``, ``, ``, `name:"mirror"`, ``, ``)), @@ -663,6 +667,21 @@ func Module() fx.Option { store, rs, logger, meterProvider.Meter("audit.index"), ) }, + // Usage store (Pebble) — dedicated secondary store for the + // usagebuilder projections. Kept physically separate from the + // readstore so operational rebuild (`ledgerctl store rebuild-usage`) + // is just a directory drop. + func(cfg Config, logger logging.Logger) (*usagestore.Store, error) { + dir := filepath.Join(cfg.DataDir, "usage") + + return usagestore.New(dir, logger, usagestore.DefaultConfig()) + }, + // Usage builder — tails the FSM audit chain to populate the usage + // store (template usage + per-ledger event counters). Notifications + // arrive via the dedicated `name:"usage"` FanOut target. + fx.Annotate(func(store *dal.Store, us *usagestore.Store, notifications *signal.Notifications, logger logging.Logger, meterProvider metric.MeterProvider) *usagebuilder.Builder { + return usagebuilder.NewBuilder(store, us, notifications, logger, meterProvider.Meter("usage.builder"), 0) + }, fx.ParamTags(``, ``, `name:"usage"`, ``, ``)), httpcompat.NewServer, func(cfg Config, logger logging.Logger, backend httpcompat.Backend, authCfg internalauth.AuthConfig, info version.Info) http.Handler { return httpcompat.NewHandler(logger, backend, authCfg, info) @@ -790,17 +809,18 @@ func Module() fx.Option { logger logging.Logger, attrs *attributes.Attributes, rs *readstore.Store, + us *usagestore.Store, coldReader *coldstorage.ColdReader, meterProvider metric.MeterProvider, ) (ctrl.Controller, *ctrl.DefaultController) { - defaultCtrl := ctrl.NewDefaultController(admission, store, logger, attrs, rs, coldReader, meterProvider.Meter("ctrl")) + defaultCtrl := ctrl.NewDefaultController(admission, store, logger, attrs, rs, us, coldReader, meterProvider.Meter("ctrl")) return NewRoutedController( defaultCtrl, raftNode, servicePool, ), defaultCtrl - }, fx.ParamTags(``, `name:"service"`, ``, ``, ``, ``, ``, `optional:"true"`, ``)), + }, fx.ParamTags(``, `name:"service"`, ``, ``, ``, ``, ``, ``, `optional:"true"`, ``)), func(serviceServer *grpcadp.ServiceServer, n *node.Node) *clusterhealth.GRPCHealthUpdater { hs := health.NewServer() healthpb.RegisterHealthServer(serviceServer.GetServer(), hs) @@ -837,6 +857,7 @@ func Module() fx.Option { runtime *dal.Store, wal *wal.DefaultWAL, rs *readstore.Store, + us *usagestore.Store, sp *spool.Default, cfg Config, logger logging.Logger, @@ -856,6 +877,11 @@ func Module() fx.Option { return rs.Close() }, }) + lc.Append(fx.Hook{ + OnStop: func(_ context.Context) error { + return us.Close() + }, + }) }, // Bloom-rebuild dispatcher: the Machine signals on its // BloomRebuildCh when a cluster-config change requires a rebuild; @@ -1338,6 +1364,21 @@ func Module() fx.Option { return nil }, + // Register Pebble usage store metrics and unregister on stop. + func(lc fx.Lifecycle, us *usagestore.Store, meterProvider metric.MeterProvider) error { + reg, err := us.RegisterMetrics(meterProvider.Meter("usagestore")) + if err != nil { + return fmt.Errorf("registering usagestore metrics: %w", err) + } + + lc.Append(fx.Hook{ + OnStop: func(_ context.Context) error { + return reg.Unregister() + }, + }) + + return nil + }, // Start and stop the index builder. // The builder has its own dedicated Notifications signal to receive // log-committed events from the FSM without competing with other consumers. @@ -1349,6 +1390,12 @@ func Module() fx.Option { func(lc fx.Lifecycle, auditIndexer *auditindexer.Indexer) { lc.Append(worker.FxHook(auditIndexer)) }, + // Start and stop the usage builder. Notifications are already + // wired at construction time (constructor injection), so this + // hook only needs the lifecycle wiring. + func(lc fx.Lifecycle, usageBuilder *usagebuilder.Builder) { + lc.Append(worker.FxHook(usageBuilder)) + }, ), ) } diff --git a/internal/domain/processing/processor_mirror.go b/internal/domain/processing/processor_mirror.go index 125044a41f..d310f89b43 100644 --- a/internal/domain/processing/processor_mirror.go +++ b/internal/domain/processing/processor_mirror.go @@ -157,7 +157,9 @@ func processMirrorCreatedTransaction(ledger string, ct *raftcmdpb.MirrorCreatedT if boundaries.GetNextTransactionId() <= txID { boundaries.NextTransactionId = txID + 1 } - boundaries.PostingCount += uint64(len(ct.GetPostings())) + + // posting_count is no longer maintained on LedgerBoundaries — the + // usagebuilder derives it from the audit chain. See EN-1420. timestamp := ct.GetTimestamp() if timestamp == nil { @@ -339,8 +341,10 @@ func processMirrorRevertedTransaction(ledger string, rt *raftcmdpb.MirrorReverte if boundaries.GetNextTransactionId() <= revertTxID { boundaries.NextTransactionId = revertTxID + 1 } - boundaries.PostingCount += uint64(len(rt.GetReversePostings())) - boundaries.RevertCount++ + + // posting_count and revert_count are no longer maintained on + // LedgerBoundaries — the usagebuilder derives them from the audit + // chain. See EN-1420. timestamp := rt.GetTimestamp() if timestamp == nil { diff --git a/internal/domain/processing/processor_revert_transaction.go b/internal/domain/processing/processor_revert_transaction.go index d9a82af858..2b7a86613c 100644 --- a/internal/domain/processing/processor_revert_transaction.go +++ b/internal/domain/processing/processor_revert_transaction.go @@ -93,8 +93,10 @@ func processRevertTransaction(ledger string, order *raftcmdpb.RevertTransactionO // Get new transaction ID for the revert transaction revertTxID := boundaries.GetNextTransactionId() boundaries.NextTransactionId = revertTxID + 1 - boundaries.PostingCount += uint64(len(revertPostings)) - boundaries.RevertCount++ + + // posting_count and revert_count are no longer maintained on + // LedgerBoundaries — the usagebuilder derives them from the audit + // chain. See EN-1420. // Resolve the revert timestamp. When at_effective_date is set, the compensating // transaction inherits the original's effective timestamp (parity with diff --git a/internal/domain/processing/processor_transaction.go b/internal/domain/processing/processor_transaction.go index d17ee54d0a..43e85b83cc 100644 --- a/internal/domain/processing/processor_transaction.go +++ b/internal/domain/processing/processor_transaction.go @@ -82,11 +82,12 @@ func processCreateTransaction(ledger string, order *raftcmdpb.CreateTransactionO nextTransactionID := boundaries.GetNextTransactionId() boundaries.NextTransactionId = nextTransactionID + 1 - boundaries.PostingCount += uint64(len(result.Postings)) - if isNumscript { - boundaries.NumscriptExecutionCount++ - } + // posting_count, numscript_execution_count, revert_count, reference_count + // are no longer maintained on LedgerBoundaries — they are derived from the + // audit chain by internal/application/usagebuilder and served by the + // LedgerStats handler out of usagestore. See EN-1420. + _ = isNumscript // Use the user-provided timestamp, or fall back to the command date. // The effective timestamp is recorded on TransactionState so reverts can diff --git a/internal/infra/state/write_set.go b/internal/infra/state/write_set.go index acdf4cde54..9e8db2302a 100644 --- a/internal/infra/state/write_set.go +++ b/internal/infra/state/write_set.go @@ -258,7 +258,7 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create } // Update per-ledger attribute counters in boundaries before merging them. - b.updateBoundaryCounters(volumeUpdates, partResult.purged, partResult.transient, metadataUpdates, metadataDeletions, referenceUpdates) + b.updateBoundaryCounters(volumeUpdates, partResult.purged, partResult.transient, metadataUpdates, metadataDeletions) boundaryUpdates, boundaryDeletions, err := b.Derived.Boundaries.Merge() if err != nil { diff --git a/internal/infra/state/write_set_counters.go b/internal/infra/state/write_set_counters.go index 65553156d4..fa9f0347cc 100644 --- a/internal/infra/state/write_set_counters.go +++ b/internal/infra/state/write_set_counters.go @@ -76,13 +76,17 @@ func countVolumeDeltas(updates []attributes.Update[domain.VolumeKey, *raftcmdpb. // updateBoundaryCounters computes attribute key deltas and updates LedgerBoundaries // for each affected ledger. Must be called before Derived.Boundaries.Merge(). +// +// Only the attribute-derived counters (volume_count, metadata_count, +// ephemeral_evicted_count, transient_used_count) live here — reference_count +// moved to the usagebuilder (see EN-1420) which derives it from the audit +// chain instead. The 4 remaining are covered by EN-1422. func (b *WriteSet) updateBoundaryCounters( volumeUpdates []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], purgedVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], transientVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], metadataUpdates []attributes.Update[domain.MetadataKey, *commonpb.MetadataValue], metadataDeletions []attributes.Deletion[domain.MetadataKey], - referenceUpdates []attributes.Update[domain.TransactionReferenceKey, *commonpb.TransactionReferenceValue], ) { volumeDeltas := countVolumeDeltas(volumeUpdates) @@ -104,7 +108,6 @@ func (b *WriteSet) updateBoundaryCounters( } metadataDeltas := countKeyDeltas(metadataUpdates, metadataDeletions, func(k domain.MetadataKey) string { return k.LedgerName }) - referenceDeltas := countKeyDeltas(referenceUpdates, nil, func(k domain.TransactionReferenceKey) string { return k.LedgerName }) // Collect all affected ledgers. affected := make(map[string]struct{}) @@ -114,9 +117,6 @@ func (b *WriteSet) updateBoundaryCounters( for ledger := range metadataDeltas { affected[ledger] = struct{}{} } - for ledger := range referenceDeltas { - affected[ledger] = struct{}{} - } for ledger := range ephemeralEvicted { affected[ledger] = struct{}{} } @@ -133,7 +133,6 @@ func (b *WriteSet) updateBoundaryCounters( boundaries := boundariesReader.Mutate() boundaries.VolumeCount = applyDelta(boundaries.GetVolumeCount(), volumeDeltas[ledgerName]) boundaries.MetadataCount = applyDelta(boundaries.GetMetadataCount(), metadataDeltas[ledgerName]) - boundaries.ReferenceCount = applyDelta(boundaries.GetReferenceCount(), referenceDeltas[ledgerName]) boundaries.EphemeralEvictedCount += ephemeralEvicted[ledgerName] boundaries.TransientUsedCount += transientUsed[ledgerName] b.Boundaries().Put(domain.LedgerKey{Name: ledgerName}, boundaries) diff --git a/internal/proto/commonpb/common.pb.go b/internal/proto/commonpb/common.pb.go index a3e5cfed84..82ad554ec3 100644 --- a/internal/proto/commonpb/common.pb.go +++ b/internal/proto/commonpb/common.pb.go @@ -4587,6 +4587,61 @@ func (x *DeletedNumscriptLog) GetLedger() string { return "" } +// TemplateUsage tracks per-template invocation usage. Persisted as a +// projection of the audit log by the usagebuilder subsystem — NOT part of +// the FSM authoritative state. Rebuildable from cursor=0 on demand. +type TemplateUsage struct { + state protoimpl.MessageState `protogen:"open.v1"` + Count uint64 `protobuf:"fixed64,1,opt,name=count,proto3" json:"count,omitempty"` + LastUsed *Timestamp `protobuf:"bytes,2,opt,name=last_used,json=lastUsed,proto3" json:"last_used,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TemplateUsage) Reset() { + *x = TemplateUsage{} + mi := &file_common_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TemplateUsage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TemplateUsage) ProtoMessage() {} + +func (x *TemplateUsage) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[47] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TemplateUsage.ProtoReflect.Descriptor instead. +func (*TemplateUsage) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{47} +} + +func (x *TemplateUsage) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *TemplateUsage) GetLastUsed() *Timestamp { + if x != nil { + return x.LastUsed + } + return nil +} + // SetQueryCheckpointScheduleLog records a query checkpoint schedule change. type SetQueryCheckpointScheduleLog struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -4597,7 +4652,7 @@ type SetQueryCheckpointScheduleLog struct { func (x *SetQueryCheckpointScheduleLog) Reset() { *x = SetQueryCheckpointScheduleLog{} - mi := &file_common_proto_msgTypes[47] + mi := &file_common_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4609,7 +4664,7 @@ func (x *SetQueryCheckpointScheduleLog) String() string { func (*SetQueryCheckpointScheduleLog) ProtoMessage() {} func (x *SetQueryCheckpointScheduleLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[47] + mi := &file_common_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4622,7 +4677,7 @@ func (x *SetQueryCheckpointScheduleLog) ProtoReflect() protoreflect.Message { // Deprecated: Use SetQueryCheckpointScheduleLog.ProtoReflect.Descriptor instead. func (*SetQueryCheckpointScheduleLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{47} + return file_common_proto_rawDescGZIP(), []int{48} } func (x *SetQueryCheckpointScheduleLog) GetCron() string { @@ -4641,7 +4696,7 @@ type DeletedQueryCheckpointScheduleLog struct { func (x *DeletedQueryCheckpointScheduleLog) Reset() { *x = DeletedQueryCheckpointScheduleLog{} - mi := &file_common_proto_msgTypes[48] + mi := &file_common_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4653,7 +4708,7 @@ func (x *DeletedQueryCheckpointScheduleLog) String() string { func (*DeletedQueryCheckpointScheduleLog) ProtoMessage() {} func (x *DeletedQueryCheckpointScheduleLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[48] + mi := &file_common_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4666,7 +4721,7 @@ func (x *DeletedQueryCheckpointScheduleLog) ProtoReflect() protoreflect.Message // Deprecated: Use DeletedQueryCheckpointScheduleLog.ProtoReflect.Descriptor instead. func (*DeletedQueryCheckpointScheduleLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{48} + return file_common_proto_rawDescGZIP(), []int{49} } // CreatedQueryCheckpointLog records a query checkpoint being created. @@ -4680,7 +4735,7 @@ type CreatedQueryCheckpointLog struct { func (x *CreatedQueryCheckpointLog) Reset() { *x = CreatedQueryCheckpointLog{} - mi := &file_common_proto_msgTypes[49] + mi := &file_common_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4692,7 +4747,7 @@ func (x *CreatedQueryCheckpointLog) String() string { func (*CreatedQueryCheckpointLog) ProtoMessage() {} func (x *CreatedQueryCheckpointLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[49] + mi := &file_common_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4705,7 +4760,7 @@ func (x *CreatedQueryCheckpointLog) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedQueryCheckpointLog.ProtoReflect.Descriptor instead. func (*CreatedQueryCheckpointLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{49} + return file_common_proto_rawDescGZIP(), []int{50} } func (x *CreatedQueryCheckpointLog) GetCheckpointId() uint64 { @@ -4732,7 +4787,7 @@ type DeletedQueryCheckpointLog struct { func (x *DeletedQueryCheckpointLog) Reset() { *x = DeletedQueryCheckpointLog{} - mi := &file_common_proto_msgTypes[50] + mi := &file_common_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4744,7 +4799,7 @@ func (x *DeletedQueryCheckpointLog) String() string { func (*DeletedQueryCheckpointLog) ProtoMessage() {} func (x *DeletedQueryCheckpointLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[50] + mi := &file_common_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4757,7 +4812,7 @@ func (x *DeletedQueryCheckpointLog) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletedQueryCheckpointLog.ProtoReflect.Descriptor instead. func (*DeletedQueryCheckpointLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{50} + return file_common_proto_rawDescGZIP(), []int{51} } func (x *DeletedQueryCheckpointLog) GetCheckpointId() uint64 { @@ -4789,7 +4844,7 @@ type SinkConfig struct { func (x *SinkConfig) Reset() { *x = SinkConfig{} - mi := &file_common_proto_msgTypes[51] + mi := &file_common_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4801,7 +4856,7 @@ func (x *SinkConfig) String() string { func (*SinkConfig) ProtoMessage() {} func (x *SinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[51] + mi := &file_common_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4814,7 +4869,7 @@ func (x *SinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SinkConfig.ProtoReflect.Descriptor instead. func (*SinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{51} + return file_common_proto_rawDescGZIP(), []int{52} } func (x *SinkConfig) GetName() string { @@ -4950,7 +5005,7 @@ type SinkStatus struct { func (x *SinkStatus) Reset() { *x = SinkStatus{} - mi := &file_common_proto_msgTypes[52] + mi := &file_common_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4962,7 +5017,7 @@ func (x *SinkStatus) String() string { func (*SinkStatus) ProtoMessage() {} func (x *SinkStatus) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[52] + mi := &file_common_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4975,7 +5030,7 @@ func (x *SinkStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SinkStatus.ProtoReflect.Descriptor instead. func (*SinkStatus) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{52} + return file_common_proto_rawDescGZIP(), []int{53} } func (x *SinkStatus) GetSinkName() string { @@ -5010,7 +5065,7 @@ type SinkError struct { func (x *SinkError) Reset() { *x = SinkError{} - mi := &file_common_proto_msgTypes[53] + mi := &file_common_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5022,7 +5077,7 @@ func (x *SinkError) String() string { func (*SinkError) ProtoMessage() {} func (x *SinkError) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[53] + mi := &file_common_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5035,7 +5090,7 @@ func (x *SinkError) ProtoReflect() protoreflect.Message { // Deprecated: Use SinkError.ProtoReflect.Descriptor instead. func (*SinkError) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{53} + return file_common_proto_rawDescGZIP(), []int{54} } func (x *SinkError) GetMessage() string { @@ -5063,7 +5118,7 @@ type NatsSinkConfig struct { func (x *NatsSinkConfig) Reset() { *x = NatsSinkConfig{} - mi := &file_common_proto_msgTypes[54] + mi := &file_common_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5075,7 +5130,7 @@ func (x *NatsSinkConfig) String() string { func (*NatsSinkConfig) ProtoMessage() {} func (x *NatsSinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[54] + mi := &file_common_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5088,7 +5143,7 @@ func (x *NatsSinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use NatsSinkConfig.ProtoReflect.Descriptor instead. func (*NatsSinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{54} + return file_common_proto_rawDescGZIP(), []int{55} } func (x *NatsSinkConfig) GetUrl() string { @@ -5116,7 +5171,7 @@ type ClickHouseSinkConfig struct { func (x *ClickHouseSinkConfig) Reset() { *x = ClickHouseSinkConfig{} - mi := &file_common_proto_msgTypes[55] + mi := &file_common_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5128,7 +5183,7 @@ func (x *ClickHouseSinkConfig) String() string { func (*ClickHouseSinkConfig) ProtoMessage() {} func (x *ClickHouseSinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[55] + mi := &file_common_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5141,7 +5196,7 @@ func (x *ClickHouseSinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ClickHouseSinkConfig.ProtoReflect.Descriptor instead. func (*ClickHouseSinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{55} + return file_common_proto_rawDescGZIP(), []int{56} } func (x *ClickHouseSinkConfig) GetDsn() string { @@ -5173,7 +5228,7 @@ type KafkaSinkConfig struct { func (x *KafkaSinkConfig) Reset() { *x = KafkaSinkConfig{} - mi := &file_common_proto_msgTypes[56] + mi := &file_common_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5185,7 +5240,7 @@ func (x *KafkaSinkConfig) String() string { func (*KafkaSinkConfig) ProtoMessage() {} func (x *KafkaSinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[56] + mi := &file_common_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5198,7 +5253,7 @@ func (x *KafkaSinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use KafkaSinkConfig.ProtoReflect.Descriptor instead. func (*KafkaSinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{56} + return file_common_proto_rawDescGZIP(), []int{57} } func (x *KafkaSinkConfig) GetBrokers() []string { @@ -5254,7 +5309,7 @@ type HttpSinkConfig struct { func (x *HttpSinkConfig) Reset() { *x = HttpSinkConfig{} - mi := &file_common_proto_msgTypes[57] + mi := &file_common_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5266,7 +5321,7 @@ func (x *HttpSinkConfig) String() string { func (*HttpSinkConfig) ProtoMessage() {} func (x *HttpSinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[57] + mi := &file_common_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5279,7 +5334,7 @@ func (x *HttpSinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use HttpSinkConfig.ProtoReflect.Descriptor instead. func (*HttpSinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{57} + return file_common_proto_rawDescGZIP(), []int{58} } func (x *HttpSinkConfig) GetEndpoint() string { @@ -5317,7 +5372,7 @@ type DatabricksSinkConfig struct { func (x *DatabricksSinkConfig) Reset() { *x = DatabricksSinkConfig{} - mi := &file_common_proto_msgTypes[58] + mi := &file_common_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5329,7 +5384,7 @@ func (x *DatabricksSinkConfig) String() string { func (*DatabricksSinkConfig) ProtoMessage() {} func (x *DatabricksSinkConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[58] + mi := &file_common_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5342,7 +5397,7 @@ func (x *DatabricksSinkConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use DatabricksSinkConfig.ProtoReflect.Descriptor instead. func (*DatabricksSinkConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{58} + return file_common_proto_rawDescGZIP(), []int{59} } func (x *DatabricksSinkConfig) GetServerHostname() string { @@ -5439,7 +5494,7 @@ type DatabricksOAuthM2M struct { func (x *DatabricksOAuthM2M) Reset() { *x = DatabricksOAuthM2M{} - mi := &file_common_proto_msgTypes[59] + mi := &file_common_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5451,7 +5506,7 @@ func (x *DatabricksOAuthM2M) String() string { func (*DatabricksOAuthM2M) ProtoMessage() {} func (x *DatabricksOAuthM2M) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[59] + mi := &file_common_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5464,7 +5519,7 @@ func (x *DatabricksOAuthM2M) ProtoReflect() protoreflect.Message { // Deprecated: Use DatabricksOAuthM2M.ProtoReflect.Descriptor instead. func (*DatabricksOAuthM2M) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{59} + return file_common_proto_rawDescGZIP(), []int{60} } func (x *DatabricksOAuthM2M) GetClientId() string { @@ -5500,7 +5555,7 @@ type CreatedLedgerLog struct { func (x *CreatedLedgerLog) Reset() { *x = CreatedLedgerLog{} - mi := &file_common_proto_msgTypes[60] + mi := &file_common_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5512,7 +5567,7 @@ func (x *CreatedLedgerLog) String() string { func (*CreatedLedgerLog) ProtoMessage() {} func (x *CreatedLedgerLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[60] + mi := &file_common_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5525,7 +5580,7 @@ func (x *CreatedLedgerLog) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedLedgerLog.ProtoReflect.Descriptor instead. func (*CreatedLedgerLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{60} + return file_common_proto_rawDescGZIP(), []int{61} } func (x *CreatedLedgerLog) GetName() string { @@ -5594,7 +5649,7 @@ type DeletedLedgerLog struct { func (x *DeletedLedgerLog) Reset() { *x = DeletedLedgerLog{} - mi := &file_common_proto_msgTypes[61] + mi := &file_common_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5606,7 +5661,7 @@ func (x *DeletedLedgerLog) String() string { func (*DeletedLedgerLog) ProtoMessage() {} func (x *DeletedLedgerLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[61] + mi := &file_common_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5619,7 +5674,7 @@ func (x *DeletedLedgerLog) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletedLedgerLog.ProtoReflect.Descriptor instead. func (*DeletedLedgerLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{61} + return file_common_proto_rawDescGZIP(), []int{62} } func (x *DeletedLedgerLog) GetName() string { @@ -5646,7 +5701,7 @@ type ApplyLedgerLog struct { func (x *ApplyLedgerLog) Reset() { *x = ApplyLedgerLog{} - mi := &file_common_proto_msgTypes[62] + mi := &file_common_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5658,7 +5713,7 @@ func (x *ApplyLedgerLog) String() string { func (*ApplyLedgerLog) ProtoMessage() {} func (x *ApplyLedgerLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[62] + mi := &file_common_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5671,7 +5726,7 @@ func (x *ApplyLedgerLog) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyLedgerLog.ProtoReflect.Descriptor instead. func (*ApplyLedgerLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{62} + return file_common_proto_rawDescGZIP(), []int{63} } func (x *ApplyLedgerLog) GetLedgerName() string { @@ -5709,7 +5764,7 @@ type LedgerLog struct { func (x *LedgerLog) Reset() { *x = LedgerLog{} - mi := &file_common_proto_msgTypes[63] + mi := &file_common_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5721,7 +5776,7 @@ func (x *LedgerLog) String() string { func (*LedgerLog) ProtoMessage() {} func (x *LedgerLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[63] + mi := &file_common_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5734,7 +5789,7 @@ func (x *LedgerLog) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerLog.ProtoReflect.Descriptor instead. func (*LedgerLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{63} + return file_common_proto_rawDescGZIP(), []int{64} } func (x *LedgerLog) GetData() *LedgerLogPayload { @@ -5778,7 +5833,7 @@ type TouchedVolume struct { func (x *TouchedVolume) Reset() { *x = TouchedVolume{} - mi := &file_common_proto_msgTypes[64] + mi := &file_common_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5790,7 +5845,7 @@ func (x *TouchedVolume) String() string { func (*TouchedVolume) ProtoMessage() {} func (x *TouchedVolume) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[64] + mi := &file_common_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5803,7 +5858,7 @@ func (x *TouchedVolume) ProtoReflect() protoreflect.Message { // Deprecated: Use TouchedVolume.ProtoReflect.Descriptor instead. func (*TouchedVolume) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{64} + return file_common_proto_rawDescGZIP(), []int{65} } func (x *TouchedVolume) GetAccount() string { @@ -5843,7 +5898,7 @@ type LedgerLogPayload struct { func (x *LedgerLogPayload) Reset() { *x = LedgerLogPayload{} - mi := &file_common_proto_msgTypes[65] + mi := &file_common_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5855,7 +5910,7 @@ func (x *LedgerLogPayload) String() string { func (*LedgerLogPayload) ProtoMessage() {} func (x *LedgerLogPayload) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[65] + mi := &file_common_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5868,7 +5923,7 @@ func (x *LedgerLogPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerLogPayload.ProtoReflect.Descriptor instead. func (*LedgerLogPayload) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{65} + return file_common_proto_rawDescGZIP(), []int{66} } func (x *LedgerLogPayload) GetPayload() isLedgerLogPayload_Payload { @@ -6072,7 +6127,7 @@ type CreatedIndexLog struct { func (x *CreatedIndexLog) Reset() { *x = CreatedIndexLog{} - mi := &file_common_proto_msgTypes[66] + mi := &file_common_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6084,7 +6139,7 @@ func (x *CreatedIndexLog) String() string { func (*CreatedIndexLog) ProtoMessage() {} func (x *CreatedIndexLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[66] + mi := &file_common_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6097,7 +6152,7 @@ func (x *CreatedIndexLog) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedIndexLog.ProtoReflect.Descriptor instead. func (*CreatedIndexLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{66} + return file_common_proto_rawDescGZIP(), []int{67} } func (x *CreatedIndexLog) GetId() *IndexID { @@ -6117,7 +6172,7 @@ type DroppedIndexLog struct { func (x *DroppedIndexLog) Reset() { *x = DroppedIndexLog{} - mi := &file_common_proto_msgTypes[67] + mi := &file_common_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6129,7 +6184,7 @@ func (x *DroppedIndexLog) String() string { func (*DroppedIndexLog) ProtoMessage() {} func (x *DroppedIndexLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[67] + mi := &file_common_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6142,7 +6197,7 @@ func (x *DroppedIndexLog) ProtoReflect() protoreflect.Message { // Deprecated: Use DroppedIndexLog.ProtoReflect.Descriptor instead. func (*DroppedIndexLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{67} + return file_common_proto_rawDescGZIP(), []int{68} } func (x *DroppedIndexLog) GetId() *IndexID { @@ -6161,7 +6216,7 @@ type FilledGapLog struct { func (x *FilledGapLog) Reset() { *x = FilledGapLog{} - mi := &file_common_proto_msgTypes[68] + mi := &file_common_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6173,7 +6228,7 @@ func (x *FilledGapLog) String() string { func (*FilledGapLog) ProtoMessage() {} func (x *FilledGapLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[68] + mi := &file_common_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6186,7 +6241,7 @@ func (x *FilledGapLog) ProtoReflect() protoreflect.Message { // Deprecated: Use FilledGapLog.ProtoReflect.Descriptor instead. func (*FilledGapLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{68} + return file_common_proto_rawDescGZIP(), []int{69} } func (x *FilledGapLog) GetOriginalId() uint64 { @@ -6208,7 +6263,7 @@ type CreatedTransaction struct { func (x *CreatedTransaction) Reset() { *x = CreatedTransaction{} - mi := &file_common_proto_msgTypes[69] + mi := &file_common_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6220,7 +6275,7 @@ func (x *CreatedTransaction) String() string { func (*CreatedTransaction) ProtoMessage() {} func (x *CreatedTransaction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[69] + mi := &file_common_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6233,7 +6288,7 @@ func (x *CreatedTransaction) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedTransaction.ProtoReflect.Descriptor instead. func (*CreatedTransaction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{69} + return file_common_proto_rawDescGZIP(), []int{70} } func (x *CreatedTransaction) GetTransaction() *Transaction { @@ -6275,7 +6330,7 @@ type RevertedTransaction struct { func (x *RevertedTransaction) Reset() { *x = RevertedTransaction{} - mi := &file_common_proto_msgTypes[70] + mi := &file_common_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6287,7 +6342,7 @@ func (x *RevertedTransaction) String() string { func (*RevertedTransaction) ProtoMessage() {} func (x *RevertedTransaction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[70] + mi := &file_common_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6300,7 +6355,7 @@ func (x *RevertedTransaction) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertedTransaction.ProtoReflect.Descriptor instead. func (*RevertedTransaction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{70} + return file_common_proto_rawDescGZIP(), []int{71} } func (x *RevertedTransaction) GetRevertedTransactionId() uint64 { @@ -6334,7 +6389,7 @@ type SavedMetadata struct { func (x *SavedMetadata) Reset() { *x = SavedMetadata{} - mi := &file_common_proto_msgTypes[71] + mi := &file_common_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6346,7 +6401,7 @@ func (x *SavedMetadata) String() string { func (*SavedMetadata) ProtoMessage() {} func (x *SavedMetadata) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[71] + mi := &file_common_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6359,7 +6414,7 @@ func (x *SavedMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use SavedMetadata.ProtoReflect.Descriptor instead. func (*SavedMetadata) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{71} + return file_common_proto_rawDescGZIP(), []int{72} } func (x *SavedMetadata) GetTarget() *Target { @@ -6386,7 +6441,7 @@ type DeletedMetadata struct { func (x *DeletedMetadata) Reset() { *x = DeletedMetadata{} - mi := &file_common_proto_msgTypes[72] + mi := &file_common_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6398,7 +6453,7 @@ func (x *DeletedMetadata) String() string { func (*DeletedMetadata) ProtoMessage() {} func (x *DeletedMetadata) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[72] + mi := &file_common_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6411,7 +6466,7 @@ func (x *DeletedMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletedMetadata.ProtoReflect.Descriptor instead. func (*DeletedMetadata) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{72} + return file_common_proto_rawDescGZIP(), []int{73} } func (x *DeletedMetadata) GetTarget() *Target { @@ -6440,7 +6495,7 @@ type SetMetadataFieldTypeLog struct { func (x *SetMetadataFieldTypeLog) Reset() { *x = SetMetadataFieldTypeLog{} - mi := &file_common_proto_msgTypes[73] + mi := &file_common_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6452,7 +6507,7 @@ func (x *SetMetadataFieldTypeLog) String() string { func (*SetMetadataFieldTypeLog) ProtoMessage() {} func (x *SetMetadataFieldTypeLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[73] + mi := &file_common_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6465,7 +6520,7 @@ func (x *SetMetadataFieldTypeLog) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMetadataFieldTypeLog.ProtoReflect.Descriptor instead. func (*SetMetadataFieldTypeLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{73} + return file_common_proto_rawDescGZIP(), []int{74} } func (x *SetMetadataFieldTypeLog) GetTargetType() TargetType { @@ -6503,7 +6558,7 @@ type RemovedMetadataFieldTypeLog struct { func (x *RemovedMetadataFieldTypeLog) Reset() { *x = RemovedMetadataFieldTypeLog{} - mi := &file_common_proto_msgTypes[74] + mi := &file_common_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6515,7 +6570,7 @@ func (x *RemovedMetadataFieldTypeLog) String() string { func (*RemovedMetadataFieldTypeLog) ProtoMessage() {} func (x *RemovedMetadataFieldTypeLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[74] + mi := &file_common_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6528,7 +6583,7 @@ func (x *RemovedMetadataFieldTypeLog) ProtoReflect() protoreflect.Message { // Deprecated: Use RemovedMetadataFieldTypeLog.ProtoReflect.Descriptor instead. func (*RemovedMetadataFieldTypeLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{74} + return file_common_proto_rawDescGZIP(), []int{75} } func (x *RemovedMetadataFieldTypeLog) GetTargetType() TargetType { @@ -6571,7 +6626,7 @@ type Chapter struct { func (x *Chapter) Reset() { *x = Chapter{} - mi := &file_common_proto_msgTypes[75] + mi := &file_common_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6583,7 +6638,7 @@ func (x *Chapter) String() string { func (*Chapter) ProtoMessage() {} func (x *Chapter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[75] + mi := &file_common_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6596,7 +6651,7 @@ func (x *Chapter) ProtoReflect() protoreflect.Message { // Deprecated: Use Chapter.ProtoReflect.Descriptor instead. func (*Chapter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{75} + return file_common_proto_rawDescGZIP(), []int{76} } func (x *Chapter) GetId() uint64 { @@ -6686,7 +6741,7 @@ type ClosedChapterLog struct { func (x *ClosedChapterLog) Reset() { *x = ClosedChapterLog{} - mi := &file_common_proto_msgTypes[76] + mi := &file_common_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6698,7 +6753,7 @@ func (x *ClosedChapterLog) String() string { func (*ClosedChapterLog) ProtoMessage() {} func (x *ClosedChapterLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[76] + mi := &file_common_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6711,7 +6766,7 @@ func (x *ClosedChapterLog) ProtoReflect() protoreflect.Message { // Deprecated: Use ClosedChapterLog.ProtoReflect.Descriptor instead. func (*ClosedChapterLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{76} + return file_common_proto_rawDescGZIP(), []int{77} } func (x *ClosedChapterLog) GetClosedChapter() *Chapter { @@ -6737,7 +6792,7 @@ type SealedChapterLog struct { func (x *SealedChapterLog) Reset() { *x = SealedChapterLog{} - mi := &file_common_proto_msgTypes[77] + mi := &file_common_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6749,7 +6804,7 @@ func (x *SealedChapterLog) String() string { func (*SealedChapterLog) ProtoMessage() {} func (x *SealedChapterLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[77] + mi := &file_common_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6762,7 +6817,7 @@ func (x *SealedChapterLog) ProtoReflect() protoreflect.Message { // Deprecated: Use SealedChapterLog.ProtoReflect.Descriptor instead. func (*SealedChapterLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{77} + return file_common_proto_rawDescGZIP(), []int{78} } func (x *SealedChapterLog) GetChapter() *Chapter { @@ -6781,7 +6836,7 @@ type ArchivedChapterLog struct { func (x *ArchivedChapterLog) Reset() { *x = ArchivedChapterLog{} - mi := &file_common_proto_msgTypes[78] + mi := &file_common_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6793,7 +6848,7 @@ func (x *ArchivedChapterLog) String() string { func (*ArchivedChapterLog) ProtoMessage() {} func (x *ArchivedChapterLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[78] + mi := &file_common_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6806,7 +6861,7 @@ func (x *ArchivedChapterLog) ProtoReflect() protoreflect.Message { // Deprecated: Use ArchivedChapterLog.ProtoReflect.Descriptor instead. func (*ArchivedChapterLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{78} + return file_common_proto_rawDescGZIP(), []int{79} } func (x *ArchivedChapterLog) GetChapter() *Chapter { @@ -6825,7 +6880,7 @@ type ConfirmedArchiveChapterLog struct { func (x *ConfirmedArchiveChapterLog) Reset() { *x = ConfirmedArchiveChapterLog{} - mi := &file_common_proto_msgTypes[79] + mi := &file_common_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6837,7 +6892,7 @@ func (x *ConfirmedArchiveChapterLog) String() string { func (*ConfirmedArchiveChapterLog) ProtoMessage() {} func (x *ConfirmedArchiveChapterLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[79] + mi := &file_common_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6850,7 +6905,7 @@ func (x *ConfirmedArchiveChapterLog) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfirmedArchiveChapterLog.ProtoReflect.Descriptor instead. func (*ConfirmedArchiveChapterLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{79} + return file_common_proto_rawDescGZIP(), []int{80} } func (x *ConfirmedArchiveChapterLog) GetChapter() *Chapter { @@ -6879,7 +6934,7 @@ type MirrorSourceConfig struct { func (x *MirrorSourceConfig) Reset() { *x = MirrorSourceConfig{} - mi := &file_common_proto_msgTypes[80] + mi := &file_common_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6891,7 +6946,7 @@ func (x *MirrorSourceConfig) String() string { func (*MirrorSourceConfig) ProtoMessage() {} func (x *MirrorSourceConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[80] + mi := &file_common_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6904,7 +6959,7 @@ func (x *MirrorSourceConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use MirrorSourceConfig.ProtoReflect.Descriptor instead. func (*MirrorSourceConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{80} + return file_common_proto_rawDescGZIP(), []int{81} } func (x *MirrorSourceConfig) GetLedgerName() string { @@ -7002,7 +7057,7 @@ type MirrorRewriteRule struct { func (x *MirrorRewriteRule) Reset() { *x = MirrorRewriteRule{} - mi := &file_common_proto_msgTypes[81] + mi := &file_common_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7014,7 +7069,7 @@ func (x *MirrorRewriteRule) String() string { func (*MirrorRewriteRule) ProtoMessage() {} func (x *MirrorRewriteRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[81] + mi := &file_common_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7027,7 +7082,7 @@ func (x *MirrorRewriteRule) ProtoReflect() protoreflect.Message { // Deprecated: Use MirrorRewriteRule.ProtoReflect.Descriptor instead. func (*MirrorRewriteRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{81} + return file_common_proto_rawDescGZIP(), []int{82} } func (x *MirrorRewriteRule) GetScope() isMirrorRewriteRule_Scope { @@ -7133,7 +7188,7 @@ type CreatedTransactionRule struct { func (x *CreatedTransactionRule) Reset() { *x = CreatedTransactionRule{} - mi := &file_common_proto_msgTypes[82] + mi := &file_common_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7145,7 +7200,7 @@ func (x *CreatedTransactionRule) String() string { func (*CreatedTransactionRule) ProtoMessage() {} func (x *CreatedTransactionRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[82] + mi := &file_common_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7158,7 +7213,7 @@ func (x *CreatedTransactionRule) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedTransactionRule.ProtoReflect.Descriptor instead. func (*CreatedTransactionRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{82} + return file_common_proto_rawDescGZIP(), []int{83} } func (x *CreatedTransactionRule) GetMatch() string { @@ -7185,7 +7240,7 @@ type RevertedTransactionRule struct { func (x *RevertedTransactionRule) Reset() { *x = RevertedTransactionRule{} - mi := &file_common_proto_msgTypes[83] + mi := &file_common_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7197,7 +7252,7 @@ func (x *RevertedTransactionRule) String() string { func (*RevertedTransactionRule) ProtoMessage() {} func (x *RevertedTransactionRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[83] + mi := &file_common_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7210,7 +7265,7 @@ func (x *RevertedTransactionRule) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertedTransactionRule.ProtoReflect.Descriptor instead. func (*RevertedTransactionRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{83} + return file_common_proto_rawDescGZIP(), []int{84} } func (x *RevertedTransactionRule) GetMatch() string { @@ -7237,7 +7292,7 @@ type SavedMetadataRule struct { func (x *SavedMetadataRule) Reset() { *x = SavedMetadataRule{} - mi := &file_common_proto_msgTypes[84] + mi := &file_common_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7249,7 +7304,7 @@ func (x *SavedMetadataRule) String() string { func (*SavedMetadataRule) ProtoMessage() {} func (x *SavedMetadataRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[84] + mi := &file_common_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7262,7 +7317,7 @@ func (x *SavedMetadataRule) ProtoReflect() protoreflect.Message { // Deprecated: Use SavedMetadataRule.ProtoReflect.Descriptor instead. func (*SavedMetadataRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{84} + return file_common_proto_rawDescGZIP(), []int{85} } func (x *SavedMetadataRule) GetMatch() string { @@ -7289,7 +7344,7 @@ type DeletedMetadataRule struct { func (x *DeletedMetadataRule) Reset() { *x = DeletedMetadataRule{} - mi := &file_common_proto_msgTypes[85] + mi := &file_common_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7301,7 +7356,7 @@ func (x *DeletedMetadataRule) String() string { func (*DeletedMetadataRule) ProtoMessage() {} func (x *DeletedMetadataRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[85] + mi := &file_common_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7314,7 +7369,7 @@ func (x *DeletedMetadataRule) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletedMetadataRule.ProtoReflect.Descriptor instead. func (*DeletedMetadataRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{85} + return file_common_proto_rawDescGZIP(), []int{86} } func (x *DeletedMetadataRule) GetMatch() string { @@ -7345,7 +7400,7 @@ type AnyVariantRule struct { func (x *AnyVariantRule) Reset() { *x = AnyVariantRule{} - mi := &file_common_proto_msgTypes[86] + mi := &file_common_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7357,7 +7412,7 @@ func (x *AnyVariantRule) String() string { func (*AnyVariantRule) ProtoMessage() {} func (x *AnyVariantRule) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[86] + mi := &file_common_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7370,7 +7425,7 @@ func (x *AnyVariantRule) ProtoReflect() protoreflect.Message { // Deprecated: Use AnyVariantRule.ProtoReflect.Descriptor instead. func (*AnyVariantRule) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{86} + return file_common_proto_rawDescGZIP(), []int{87} } func (x *AnyVariantRule) GetMatch() string { @@ -7405,7 +7460,7 @@ type CreatedTransactionAction struct { func (x *CreatedTransactionAction) Reset() { *x = CreatedTransactionAction{} - mi := &file_common_proto_msgTypes[87] + mi := &file_common_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7417,7 +7472,7 @@ func (x *CreatedTransactionAction) String() string { func (*CreatedTransactionAction) ProtoMessage() {} func (x *CreatedTransactionAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[87] + mi := &file_common_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7430,7 +7485,7 @@ func (x *CreatedTransactionAction) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatedTransactionAction.ProtoReflect.Descriptor instead. func (*CreatedTransactionAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{87} + return file_common_proto_rawDescGZIP(), []int{88} } func (x *CreatedTransactionAction) GetAction() isCreatedTransactionAction_Action { @@ -7564,7 +7619,7 @@ type RevertedTransactionAction struct { func (x *RevertedTransactionAction) Reset() { *x = RevertedTransactionAction{} - mi := &file_common_proto_msgTypes[88] + mi := &file_common_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7576,7 +7631,7 @@ func (x *RevertedTransactionAction) String() string { func (*RevertedTransactionAction) ProtoMessage() {} func (x *RevertedTransactionAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[88] + mi := &file_common_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7589,7 +7644,7 @@ func (x *RevertedTransactionAction) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertedTransactionAction.ProtoReflect.Descriptor instead. func (*RevertedTransactionAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{88} + return file_common_proto_rawDescGZIP(), []int{89} } func (x *RevertedTransactionAction) GetAction() isRevertedTransactionAction_Action { @@ -7678,7 +7733,7 @@ type SavedMetadataAction struct { func (x *SavedMetadataAction) Reset() { *x = SavedMetadataAction{} - mi := &file_common_proto_msgTypes[89] + mi := &file_common_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7690,7 +7745,7 @@ func (x *SavedMetadataAction) String() string { func (*SavedMetadataAction) ProtoMessage() {} func (x *SavedMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[89] + mi := &file_common_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7703,7 +7758,7 @@ func (x *SavedMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use SavedMetadataAction.ProtoReflect.Descriptor instead. func (*SavedMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{89} + return file_common_proto_rawDescGZIP(), []int{90} } func (x *SavedMetadataAction) GetAction() isSavedMetadataAction_Action { @@ -7790,7 +7845,7 @@ type DeletedMetadataAction struct { func (x *DeletedMetadataAction) Reset() { *x = DeletedMetadataAction{} - mi := &file_common_proto_msgTypes[90] + mi := &file_common_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7802,7 +7857,7 @@ func (x *DeletedMetadataAction) String() string { func (*DeletedMetadataAction) ProtoMessage() {} func (x *DeletedMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[90] + mi := &file_common_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7815,7 +7870,7 @@ func (x *DeletedMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletedMetadataAction.ProtoReflect.Descriptor instead. func (*DeletedMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{90} + return file_common_proto_rawDescGZIP(), []int{91} } func (x *DeletedMetadataAction) GetAction() isDeletedMetadataAction_Action { @@ -7872,7 +7927,7 @@ type AnyVariantAction struct { func (x *AnyVariantAction) Reset() { *x = AnyVariantAction{} - mi := &file_common_proto_msgTypes[91] + mi := &file_common_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7884,7 +7939,7 @@ func (x *AnyVariantAction) String() string { func (*AnyVariantAction) ProtoMessage() {} func (x *AnyVariantAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[91] + mi := &file_common_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7897,7 +7952,7 @@ func (x *AnyVariantAction) ProtoReflect() protoreflect.Message { // Deprecated: Use AnyVariantAction.ProtoReflect.Descriptor instead. func (*AnyVariantAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{91} + return file_common_proto_rawDescGZIP(), []int{92} } func (x *AnyVariantAction) GetAction() isAnyVariantAction_Action { @@ -7951,7 +8006,7 @@ type RewriteAddressAction struct { func (x *RewriteAddressAction) Reset() { *x = RewriteAddressAction{} - mi := &file_common_proto_msgTypes[92] + mi := &file_common_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7963,7 +8018,7 @@ func (x *RewriteAddressAction) String() string { func (*RewriteAddressAction) ProtoMessage() {} func (x *RewriteAddressAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[92] + mi := &file_common_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7976,7 +8031,7 @@ func (x *RewriteAddressAction) ProtoReflect() protoreflect.Message { // Deprecated: Use RewriteAddressAction.ProtoReflect.Descriptor instead. func (*RewriteAddressAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{92} + return file_common_proto_rawDescGZIP(), []int{93} } func (x *RewriteAddressAction) GetPattern() string { @@ -8019,7 +8074,7 @@ type SetMetadataAction struct { func (x *SetMetadataAction) Reset() { *x = SetMetadataAction{} - mi := &file_common_proto_msgTypes[93] + mi := &file_common_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8031,7 +8086,7 @@ func (x *SetMetadataAction) String() string { func (*SetMetadataAction) ProtoMessage() {} func (x *SetMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[93] + mi := &file_common_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8044,7 +8099,7 @@ func (x *SetMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use SetMetadataAction.ProtoReflect.Descriptor instead. func (*SetMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{93} + return file_common_proto_rawDescGZIP(), []int{94} } func (x *SetMetadataAction) GetKey() string { @@ -8111,7 +8166,7 @@ type DeleteMetadataAction struct { func (x *DeleteMetadataAction) Reset() { *x = DeleteMetadataAction{} - mi := &file_common_proto_msgTypes[94] + mi := &file_common_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8123,7 +8178,7 @@ func (x *DeleteMetadataAction) String() string { func (*DeleteMetadataAction) ProtoMessage() {} func (x *DeleteMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[94] + mi := &file_common_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8136,7 +8191,7 @@ func (x *DeleteMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMetadataAction.ProtoReflect.Descriptor instead. func (*DeleteMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{94} + return file_common_proto_rawDescGZIP(), []int{95} } func (x *DeleteMetadataAction) GetKey() string { @@ -8165,7 +8220,7 @@ type SetAccountMetadataAction struct { func (x *SetAccountMetadataAction) Reset() { *x = SetAccountMetadataAction{} - mi := &file_common_proto_msgTypes[95] + mi := &file_common_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8177,7 +8232,7 @@ func (x *SetAccountMetadataAction) String() string { func (*SetAccountMetadataAction) ProtoMessage() {} func (x *SetAccountMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[95] + mi := &file_common_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8190,7 +8245,7 @@ func (x *SetAccountMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use SetAccountMetadataAction.ProtoReflect.Descriptor instead. func (*SetAccountMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{95} + return file_common_proto_rawDescGZIP(), []int{96} } func (x *SetAccountMetadataAction) GetAccount() string { @@ -8265,7 +8320,7 @@ type DeleteAccountMetadataAction struct { func (x *DeleteAccountMetadataAction) Reset() { *x = DeleteAccountMetadataAction{} - mi := &file_common_proto_msgTypes[96] + mi := &file_common_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8277,7 +8332,7 @@ func (x *DeleteAccountMetadataAction) String() string { func (*DeleteAccountMetadataAction) ProtoMessage() {} func (x *DeleteAccountMetadataAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[96] + mi := &file_common_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8290,7 +8345,7 @@ func (x *DeleteAccountMetadataAction) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAccountMetadataAction.ProtoReflect.Descriptor instead. func (*DeleteAccountMetadataAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{96} + return file_common_proto_rawDescGZIP(), []int{97} } func (x *DeleteAccountMetadataAction) GetAccount() string { @@ -8323,7 +8378,7 @@ type SetAccountMetadataFromAddressAction struct { func (x *SetAccountMetadataFromAddressAction) Reset() { *x = SetAccountMetadataFromAddressAction{} - mi := &file_common_proto_msgTypes[97] + mi := &file_common_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8335,7 +8390,7 @@ func (x *SetAccountMetadataFromAddressAction) String() string { func (*SetAccountMetadataFromAddressAction) ProtoMessage() {} func (x *SetAccountMetadataFromAddressAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[97] + mi := &file_common_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8348,7 +8403,7 @@ func (x *SetAccountMetadataFromAddressAction) ProtoReflect() protoreflect.Messag // Deprecated: Use SetAccountMetadataFromAddressAction.ProtoReflect.Descriptor instead. func (*SetAccountMetadataFromAddressAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{97} + return file_common_proto_rawDescGZIP(), []int{98} } func (x *SetAccountMetadataFromAddressAction) GetPattern() string { @@ -8376,7 +8431,7 @@ type SetAccountMetadataFromAddressReplacement struct { func (x *SetAccountMetadataFromAddressReplacement) Reset() { *x = SetAccountMetadataFromAddressReplacement{} - mi := &file_common_proto_msgTypes[98] + mi := &file_common_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8388,7 +8443,7 @@ func (x *SetAccountMetadataFromAddressReplacement) String() string { func (*SetAccountMetadataFromAddressReplacement) ProtoMessage() {} func (x *SetAccountMetadataFromAddressReplacement) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[98] + mi := &file_common_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8401,7 +8456,7 @@ func (x *SetAccountMetadataFromAddressReplacement) ProtoReflect() protoreflect.M // Deprecated: Use SetAccountMetadataFromAddressReplacement.ProtoReflect.Descriptor instead. func (*SetAccountMetadataFromAddressReplacement) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{98} + return file_common_proto_rawDescGZIP(), []int{99} } func (x *SetAccountMetadataFromAddressReplacement) GetKey() string { @@ -8433,7 +8488,7 @@ type DropAction struct { func (x *DropAction) Reset() { *x = DropAction{} - mi := &file_common_proto_msgTypes[99] + mi := &file_common_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8445,7 +8500,7 @@ func (x *DropAction) String() string { func (*DropAction) ProtoMessage() {} func (x *DropAction) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[99] + mi := &file_common_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8458,7 +8513,7 @@ func (x *DropAction) ProtoReflect() protoreflect.Message { // Deprecated: Use DropAction.ProtoReflect.Descriptor instead. func (*DropAction) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{99} + return file_common_proto_rawDescGZIP(), []int{100} } type HttpMirrorSourceConfig struct { @@ -8471,7 +8526,7 @@ type HttpMirrorSourceConfig struct { func (x *HttpMirrorSourceConfig) Reset() { *x = HttpMirrorSourceConfig{} - mi := &file_common_proto_msgTypes[100] + mi := &file_common_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8483,7 +8538,7 @@ func (x *HttpMirrorSourceConfig) String() string { func (*HttpMirrorSourceConfig) ProtoMessage() {} func (x *HttpMirrorSourceConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[100] + mi := &file_common_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8496,7 +8551,7 @@ func (x *HttpMirrorSourceConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use HttpMirrorSourceConfig.ProtoReflect.Descriptor instead. func (*HttpMirrorSourceConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{100} + return file_common_proto_rawDescGZIP(), []int{101} } func (x *HttpMirrorSourceConfig) GetBaseUrl() string { @@ -8525,7 +8580,7 @@ type OAuth2ClientCredentials struct { func (x *OAuth2ClientCredentials) Reset() { *x = OAuth2ClientCredentials{} - mi := &file_common_proto_msgTypes[101] + mi := &file_common_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8537,7 +8592,7 @@ func (x *OAuth2ClientCredentials) String() string { func (*OAuth2ClientCredentials) ProtoMessage() {} func (x *OAuth2ClientCredentials) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[101] + mi := &file_common_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8550,7 +8605,7 @@ func (x *OAuth2ClientCredentials) ProtoReflect() protoreflect.Message { // Deprecated: Use OAuth2ClientCredentials.ProtoReflect.Descriptor instead. func (*OAuth2ClientCredentials) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{101} + return file_common_proto_rawDescGZIP(), []int{102} } func (x *OAuth2ClientCredentials) GetClientId() string { @@ -8596,7 +8651,7 @@ type PostgresMirrorSourceConfig struct { func (x *PostgresMirrorSourceConfig) Reset() { *x = PostgresMirrorSourceConfig{} - mi := &file_common_proto_msgTypes[102] + mi := &file_common_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8608,7 +8663,7 @@ func (x *PostgresMirrorSourceConfig) String() string { func (*PostgresMirrorSourceConfig) ProtoMessage() {} func (x *PostgresMirrorSourceConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[102] + mi := &file_common_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8621,7 +8676,7 @@ func (x *PostgresMirrorSourceConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PostgresMirrorSourceConfig.ProtoReflect.Descriptor instead. func (*PostgresMirrorSourceConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{102} + return file_common_proto_rawDescGZIP(), []int{103} } func (x *PostgresMirrorSourceConfig) GetDsn() string { @@ -8654,7 +8709,7 @@ type PostgresAwsIamAuth struct { func (x *PostgresAwsIamAuth) Reset() { *x = PostgresAwsIamAuth{} - mi := &file_common_proto_msgTypes[103] + mi := &file_common_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8666,7 +8721,7 @@ func (x *PostgresAwsIamAuth) String() string { func (*PostgresAwsIamAuth) ProtoMessage() {} func (x *PostgresAwsIamAuth) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[103] + mi := &file_common_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8679,7 +8734,7 @@ func (x *PostgresAwsIamAuth) ProtoReflect() protoreflect.Message { // Deprecated: Use PostgresAwsIamAuth.ProtoReflect.Descriptor instead. func (*PostgresAwsIamAuth) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{103} + return file_common_proto_rawDescGZIP(), []int{104} } func (x *PostgresAwsIamAuth) GetRegion() string { @@ -8706,7 +8761,7 @@ type MirrorSyncError struct { func (x *MirrorSyncError) Reset() { *x = MirrorSyncError{} - mi := &file_common_proto_msgTypes[104] + mi := &file_common_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8718,7 +8773,7 @@ func (x *MirrorSyncError) String() string { func (*MirrorSyncError) ProtoMessage() {} func (x *MirrorSyncError) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[104] + mi := &file_common_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8731,7 +8786,7 @@ func (x *MirrorSyncError) ProtoReflect() protoreflect.Message { // Deprecated: Use MirrorSyncError.ProtoReflect.Descriptor instead. func (*MirrorSyncError) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{104} + return file_common_proto_rawDescGZIP(), []int{105} } func (x *MirrorSyncError) GetMessage() string { @@ -8761,7 +8816,7 @@ type MirrorSyncProgress struct { func (x *MirrorSyncProgress) Reset() { *x = MirrorSyncProgress{} - mi := &file_common_proto_msgTypes[105] + mi := &file_common_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8773,7 +8828,7 @@ func (x *MirrorSyncProgress) String() string { func (*MirrorSyncProgress) ProtoMessage() {} func (x *MirrorSyncProgress) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[105] + mi := &file_common_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8786,7 +8841,7 @@ func (x *MirrorSyncProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use MirrorSyncProgress.ProtoReflect.Descriptor instead. func (*MirrorSyncProgress) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{105} + return file_common_proto_rawDescGZIP(), []int{106} } func (x *MirrorSyncProgress) GetState() MirrorSyncState { @@ -8847,7 +8902,7 @@ type LedgerInfo struct { func (x *LedgerInfo) Reset() { *x = LedgerInfo{} - mi := &file_common_proto_msgTypes[106] + mi := &file_common_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8859,7 +8914,7 @@ func (x *LedgerInfo) String() string { func (*LedgerInfo) ProtoMessage() {} func (x *LedgerInfo) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[106] + mi := &file_common_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8872,7 +8927,7 @@ func (x *LedgerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerInfo.ProtoReflect.Descriptor instead. func (*LedgerInfo) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{106} + return file_common_proto_rawDescGZIP(), []int{107} } func (x *LedgerInfo) GetName() string { @@ -8963,7 +9018,7 @@ type SaveMetadataCommand struct { func (x *SaveMetadataCommand) Reset() { *x = SaveMetadataCommand{} - mi := &file_common_proto_msgTypes[107] + mi := &file_common_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8975,7 +9030,7 @@ func (x *SaveMetadataCommand) String() string { func (*SaveMetadataCommand) ProtoMessage() {} func (x *SaveMetadataCommand) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[107] + mi := &file_common_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8988,7 +9043,7 @@ func (x *SaveMetadataCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveMetadataCommand.ProtoReflect.Descriptor instead. func (*SaveMetadataCommand) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{107} + return file_common_proto_rawDescGZIP(), []int{108} } func (x *SaveMetadataCommand) GetTarget() *Target { @@ -9016,7 +9071,7 @@ type DeleteMetadataCommand struct { func (x *DeleteMetadataCommand) Reset() { *x = DeleteMetadataCommand{} - mi := &file_common_proto_msgTypes[108] + mi := &file_common_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9028,7 +9083,7 @@ func (x *DeleteMetadataCommand) String() string { func (*DeleteMetadataCommand) ProtoMessage() {} func (x *DeleteMetadataCommand) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[108] + mi := &file_common_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9041,7 +9096,7 @@ func (x *DeleteMetadataCommand) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteMetadataCommand.ProtoReflect.Descriptor instead. func (*DeleteMetadataCommand) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{108} + return file_common_proto_rawDescGZIP(), []int{109} } func (x *DeleteMetadataCommand) GetTarget() *Target { @@ -9087,7 +9142,7 @@ type TransactionState struct { func (x *TransactionState) Reset() { *x = TransactionState{} - mi := &file_common_proto_msgTypes[109] + mi := &file_common_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9099,7 +9154,7 @@ func (x *TransactionState) String() string { func (*TransactionState) ProtoMessage() {} func (x *TransactionState) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[109] + mi := &file_common_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9112,7 +9167,7 @@ func (x *TransactionState) ProtoReflect() protoreflect.Message { // Deprecated: Use TransactionState.ProtoReflect.Descriptor instead. func (*TransactionState) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{109} + return file_common_proto_rawDescGZIP(), []int{110} } func (x *TransactionState) GetCreatedByLog() uint64 { @@ -9183,7 +9238,7 @@ type IdempotencyKeyValue struct { func (x *IdempotencyKeyValue) Reset() { *x = IdempotencyKeyValue{} - mi := &file_common_proto_msgTypes[110] + mi := &file_common_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9195,7 +9250,7 @@ func (x *IdempotencyKeyValue) String() string { func (*IdempotencyKeyValue) ProtoMessage() {} func (x *IdempotencyKeyValue) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[110] + mi := &file_common_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9208,7 +9263,7 @@ func (x *IdempotencyKeyValue) ProtoReflect() protoreflect.Message { // Deprecated: Use IdempotencyKeyValue.ProtoReflect.Descriptor instead. func (*IdempotencyKeyValue) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{110} + return file_common_proto_rawDescGZIP(), []int{111} } func (x *IdempotencyKeyValue) GetFirstLogSequence() uint64 { @@ -9269,7 +9324,7 @@ type IdempotencyFailure struct { func (x *IdempotencyFailure) Reset() { *x = IdempotencyFailure{} - mi := &file_common_proto_msgTypes[111] + mi := &file_common_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9281,7 +9336,7 @@ func (x *IdempotencyFailure) String() string { func (*IdempotencyFailure) ProtoMessage() {} func (x *IdempotencyFailure) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[111] + mi := &file_common_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9294,7 +9349,7 @@ func (x *IdempotencyFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use IdempotencyFailure.ProtoReflect.Descriptor instead. func (*IdempotencyFailure) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{111} + return file_common_proto_rawDescGZIP(), []int{112} } func (x *IdempotencyFailure) GetReason() ErrorReason { @@ -9328,7 +9383,7 @@ type TransactionReferenceValue struct { func (x *TransactionReferenceValue) Reset() { *x = TransactionReferenceValue{} - mi := &file_common_proto_msgTypes[112] + mi := &file_common_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9340,7 +9395,7 @@ func (x *TransactionReferenceValue) String() string { func (*TransactionReferenceValue) ProtoMessage() {} func (x *TransactionReferenceValue) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[112] + mi := &file_common_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9353,7 +9408,7 @@ func (x *TransactionReferenceValue) ProtoReflect() protoreflect.Message { // Deprecated: Use TransactionReferenceValue.ProtoReflect.Descriptor instead. func (*TransactionReferenceValue) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{112} + return file_common_proto_rawDescGZIP(), []int{113} } func (x *TransactionReferenceValue) GetTransactionId() uint64 { @@ -9373,7 +9428,7 @@ type NumscriptVersionValue struct { func (x *NumscriptVersionValue) Reset() { *x = NumscriptVersionValue{} - mi := &file_common_proto_msgTypes[113] + mi := &file_common_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9385,7 +9440,7 @@ func (x *NumscriptVersionValue) String() string { func (*NumscriptVersionValue) ProtoMessage() {} func (x *NumscriptVersionValue) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[113] + mi := &file_common_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9398,7 +9453,7 @@ func (x *NumscriptVersionValue) ProtoReflect() protoreflect.Message { // Deprecated: Use NumscriptVersionValue.ProtoReflect.Descriptor instead. func (*NumscriptVersionValue) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{113} + return file_common_proto_rawDescGZIP(), []int{114} } func (x *NumscriptVersionValue) GetVersion() string { @@ -9425,7 +9480,7 @@ type SegmentType struct { func (x *SegmentType) Reset() { *x = SegmentType{} - mi := &file_common_proto_msgTypes[114] + mi := &file_common_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9437,7 +9492,7 @@ func (x *SegmentType) String() string { func (*SegmentType) ProtoMessage() {} func (x *SegmentType) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[114] + mi := &file_common_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9450,7 +9505,7 @@ func (x *SegmentType) ProtoReflect() protoreflect.Message { // Deprecated: Use SegmentType.ProtoReflect.Descriptor instead. func (*SegmentType) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{114} + return file_common_proto_rawDescGZIP(), []int{115} } func (x *SegmentType) GetConstraint() isSegmentType_Constraint { @@ -9532,7 +9587,7 @@ type UUIDConstraint struct { func (x *UUIDConstraint) Reset() { *x = UUIDConstraint{} - mi := &file_common_proto_msgTypes[115] + mi := &file_common_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9544,7 +9599,7 @@ func (x *UUIDConstraint) String() string { func (*UUIDConstraint) ProtoMessage() {} func (x *UUIDConstraint) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[115] + mi := &file_common_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9557,7 +9612,7 @@ func (x *UUIDConstraint) ProtoReflect() protoreflect.Message { // Deprecated: Use UUIDConstraint.ProtoReflect.Descriptor instead. func (*UUIDConstraint) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{115} + return file_common_proto_rawDescGZIP(), []int{116} } type Uint64Constraint struct { @@ -9568,7 +9623,7 @@ type Uint64Constraint struct { func (x *Uint64Constraint) Reset() { *x = Uint64Constraint{} - mi := &file_common_proto_msgTypes[116] + mi := &file_common_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9580,7 +9635,7 @@ func (x *Uint64Constraint) String() string { func (*Uint64Constraint) ProtoMessage() {} func (x *Uint64Constraint) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[116] + mi := &file_common_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9593,7 +9648,7 @@ func (x *Uint64Constraint) ProtoReflect() protoreflect.Message { // Deprecated: Use Uint64Constraint.ProtoReflect.Descriptor instead. func (*Uint64Constraint) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{116} + return file_common_proto_rawDescGZIP(), []int{117} } type BytesConstraint struct { @@ -9604,7 +9659,7 @@ type BytesConstraint struct { func (x *BytesConstraint) Reset() { *x = BytesConstraint{} - mi := &file_common_proto_msgTypes[117] + mi := &file_common_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9616,7 +9671,7 @@ func (x *BytesConstraint) String() string { func (*BytesConstraint) ProtoMessage() {} func (x *BytesConstraint) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[117] + mi := &file_common_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9629,7 +9684,7 @@ func (x *BytesConstraint) ProtoReflect() protoreflect.Message { // Deprecated: Use BytesConstraint.ProtoReflect.Descriptor instead. func (*BytesConstraint) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{117} + return file_common_proto_rawDescGZIP(), []int{118} } // AccountType defines a single account address pattern for a ledger. @@ -9645,7 +9700,7 @@ type AccountType struct { func (x *AccountType) Reset() { *x = AccountType{} - mi := &file_common_proto_msgTypes[118] + mi := &file_common_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9657,7 +9712,7 @@ func (x *AccountType) String() string { func (*AccountType) ProtoMessage() {} func (x *AccountType) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[118] + mi := &file_common_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9670,7 +9725,7 @@ func (x *AccountType) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountType.ProtoReflect.Descriptor instead. func (*AccountType) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{118} + return file_common_proto_rawDescGZIP(), []int{119} } func (x *AccountType) GetName() string { @@ -9711,7 +9766,7 @@ type AddedAccountTypeLog struct { func (x *AddedAccountTypeLog) Reset() { *x = AddedAccountTypeLog{} - mi := &file_common_proto_msgTypes[119] + mi := &file_common_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9723,7 +9778,7 @@ func (x *AddedAccountTypeLog) String() string { func (*AddedAccountTypeLog) ProtoMessage() {} func (x *AddedAccountTypeLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[119] + mi := &file_common_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9736,7 +9791,7 @@ func (x *AddedAccountTypeLog) ProtoReflect() protoreflect.Message { // Deprecated: Use AddedAccountTypeLog.ProtoReflect.Descriptor instead. func (*AddedAccountTypeLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{119} + return file_common_proto_rawDescGZIP(), []int{120} } func (x *AddedAccountTypeLog) GetAccountType() *AccountType { @@ -9756,7 +9811,7 @@ type RemovedAccountTypeLog struct { func (x *RemovedAccountTypeLog) Reset() { *x = RemovedAccountTypeLog{} - mi := &file_common_proto_msgTypes[120] + mi := &file_common_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9768,7 +9823,7 @@ func (x *RemovedAccountTypeLog) String() string { func (*RemovedAccountTypeLog) ProtoMessage() {} func (x *RemovedAccountTypeLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[120] + mi := &file_common_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9781,7 +9836,7 @@ func (x *RemovedAccountTypeLog) ProtoReflect() protoreflect.Message { // Deprecated: Use RemovedAccountTypeLog.ProtoReflect.Descriptor instead. func (*RemovedAccountTypeLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{120} + return file_common_proto_rawDescGZIP(), []int{121} } func (x *RemovedAccountTypeLog) GetName() string { @@ -9801,7 +9856,7 @@ type UpdatedDefaultEnforcementModeLog struct { func (x *UpdatedDefaultEnforcementModeLog) Reset() { *x = UpdatedDefaultEnforcementModeLog{} - mi := &file_common_proto_msgTypes[121] + mi := &file_common_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9813,7 +9868,7 @@ func (x *UpdatedDefaultEnforcementModeLog) String() string { func (*UpdatedDefaultEnforcementModeLog) ProtoMessage() {} func (x *UpdatedDefaultEnforcementModeLog) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[121] + mi := &file_common_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9826,7 +9881,7 @@ func (x *UpdatedDefaultEnforcementModeLog) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatedDefaultEnforcementModeLog.ProtoReflect.Descriptor instead. func (*UpdatedDefaultEnforcementModeLog) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{121} + return file_common_proto_rawDescGZIP(), []int{122} } func (x *UpdatedDefaultEnforcementModeLog) GetEnforcementMode() ChartEnforcementMode { @@ -9860,7 +9915,7 @@ type QueryFilter struct { func (x *QueryFilter) Reset() { *x = QueryFilter{} - mi := &file_common_proto_msgTypes[122] + mi := &file_common_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9872,7 +9927,7 @@ func (x *QueryFilter) String() string { func (*QueryFilter) ProtoMessage() {} func (x *QueryFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[122] + mi := &file_common_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9885,7 +9940,7 @@ func (x *QueryFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryFilter.ProtoReflect.Descriptor instead. func (*QueryFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{122} + return file_common_proto_rawDescGZIP(), []int{123} } func (x *QueryFilter) GetFilter() isQueryFilter_Filter { @@ -10104,7 +10159,7 @@ type ReferenceCondition struct { func (x *ReferenceCondition) Reset() { *x = ReferenceCondition{} - mi := &file_common_proto_msgTypes[123] + mi := &file_common_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10116,7 +10171,7 @@ func (x *ReferenceCondition) String() string { func (*ReferenceCondition) ProtoMessage() {} func (x *ReferenceCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[123] + mi := &file_common_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10129,7 +10184,7 @@ func (x *ReferenceCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use ReferenceCondition.ProtoReflect.Descriptor instead. func (*ReferenceCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{123} + return file_common_proto_rawDescGZIP(), []int{124} } func (x *ReferenceCondition) GetCond() *StringCondition { @@ -10151,7 +10206,7 @@ type RevertedCondition struct { func (x *RevertedCondition) Reset() { *x = RevertedCondition{} - mi := &file_common_proto_msgTypes[124] + mi := &file_common_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10163,7 +10218,7 @@ func (x *RevertedCondition) String() string { func (*RevertedCondition) ProtoMessage() {} func (x *RevertedCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[124] + mi := &file_common_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10176,7 +10231,7 @@ func (x *RevertedCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertedCondition.ProtoReflect.Descriptor instead. func (*RevertedCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{124} + return file_common_proto_rawDescGZIP(), []int{125} } func (x *RevertedCondition) GetValue() bool { @@ -10214,7 +10269,7 @@ type AuditCondition struct { func (x *AuditCondition) Reset() { *x = AuditCondition{} - mi := &file_common_proto_msgTypes[125] + mi := &file_common_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10226,7 +10281,7 @@ func (x *AuditCondition) String() string { func (*AuditCondition) ProtoMessage() {} func (x *AuditCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[125] + mi := &file_common_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10239,7 +10294,7 @@ func (x *AuditCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AuditCondition.ProtoReflect.Descriptor instead. func (*AuditCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{125} + return file_common_proto_rawDescGZIP(), []int{126} } func (x *AuditCondition) GetField() AuditField { @@ -10300,7 +10355,7 @@ type LedgerCondition struct { func (x *LedgerCondition) Reset() { *x = LedgerCondition{} - mi := &file_common_proto_msgTypes[126] + mi := &file_common_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10312,7 +10367,7 @@ func (x *LedgerCondition) String() string { func (*LedgerCondition) ProtoMessage() {} func (x *LedgerCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[126] + mi := &file_common_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10325,7 +10380,7 @@ func (x *LedgerCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerCondition.ProtoReflect.Descriptor instead. func (*LedgerCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{126} + return file_common_proto_rawDescGZIP(), []int{127} } func (x *LedgerCondition) GetCond() *StringCondition { @@ -10345,7 +10400,7 @@ type LogIdCondition struct { func (x *LogIdCondition) Reset() { *x = LogIdCondition{} - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10357,7 +10412,7 @@ func (x *LogIdCondition) String() string { func (*LogIdCondition) ProtoMessage() {} func (x *LogIdCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10370,7 +10425,7 @@ func (x *LogIdCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogIdCondition.ProtoReflect.Descriptor instead. func (*LogIdCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{127} + return file_common_proto_rawDescGZIP(), []int{128} } func (x *LogIdCondition) GetCond() *UintCondition { @@ -10391,7 +10446,7 @@ type BuiltinUintCondition struct { func (x *BuiltinUintCondition) Reset() { *x = BuiltinUintCondition{} - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10403,7 +10458,7 @@ func (x *BuiltinUintCondition) String() string { func (*BuiltinUintCondition) ProtoMessage() {} func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10416,7 +10471,7 @@ func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BuiltinUintCondition.ProtoReflect.Descriptor instead. func (*BuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{128} + return file_common_proto_rawDescGZIP(), []int{129} } func (x *BuiltinUintCondition) GetField() TransactionBuiltinIndex { @@ -10444,7 +10499,7 @@ type LogBuiltinUintCondition struct { func (x *LogBuiltinUintCondition) Reset() { *x = LogBuiltinUintCondition{} - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10456,7 +10511,7 @@ func (x *LogBuiltinUintCondition) String() string { func (*LogBuiltinUintCondition) ProtoMessage() {} func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10469,7 +10524,7 @@ func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogBuiltinUintCondition.ProtoReflect.Descriptor instead. func (*LogBuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{129} + return file_common_proto_rawDescGZIP(), []int{130} } func (x *LogBuiltinUintCondition) GetField() LogBuiltinIndex { @@ -10500,7 +10555,7 @@ type AccountHasAssetCondition struct { func (x *AccountHasAssetCondition) Reset() { *x = AccountHasAssetCondition{} - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10512,7 +10567,7 @@ func (x *AccountHasAssetCondition) String() string { func (*AccountHasAssetCondition) ProtoMessage() {} func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10525,7 +10580,7 @@ func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountHasAssetCondition.ProtoReflect.Descriptor instead. func (*AccountHasAssetCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{130} + return file_common_proto_rawDescGZIP(), []int{131} } func (x *AccountHasAssetCondition) GetAssetBase() string { @@ -10551,7 +10606,7 @@ type AndFilter struct { func (x *AndFilter) Reset() { *x = AndFilter{} - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10563,7 +10618,7 @@ func (x *AndFilter) String() string { func (*AndFilter) ProtoMessage() {} func (x *AndFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10576,7 +10631,7 @@ func (x *AndFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use AndFilter.ProtoReflect.Descriptor instead. func (*AndFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{131} + return file_common_proto_rawDescGZIP(), []int{132} } func (x *AndFilter) GetFilters() []*QueryFilter { @@ -10595,7 +10650,7 @@ type OrFilter struct { func (x *OrFilter) Reset() { *x = OrFilter{} - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10607,7 +10662,7 @@ func (x *OrFilter) String() string { func (*OrFilter) ProtoMessage() {} func (x *OrFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10620,7 +10675,7 @@ func (x *OrFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use OrFilter.ProtoReflect.Descriptor instead. func (*OrFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{132} + return file_common_proto_rawDescGZIP(), []int{133} } func (x *OrFilter) GetFilters() []*QueryFilter { @@ -10639,7 +10694,7 @@ type NotFilter struct { func (x *NotFilter) Reset() { *x = NotFilter{} - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10651,7 +10706,7 @@ func (x *NotFilter) String() string { func (*NotFilter) ProtoMessage() {} func (x *NotFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10664,7 +10719,7 @@ func (x *NotFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use NotFilter.ProtoReflect.Descriptor instead. func (*NotFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{133} + return file_common_proto_rawDescGZIP(), []int{134} } func (x *NotFilter) GetFilter() *QueryFilter { @@ -10686,7 +10741,7 @@ type FieldRef struct { func (x *FieldRef) Reset() { *x = FieldRef{} - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10698,7 +10753,7 @@ func (x *FieldRef) String() string { func (*FieldRef) ProtoMessage() {} func (x *FieldRef) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10711,7 +10766,7 @@ func (x *FieldRef) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldRef.ProtoReflect.Descriptor instead. func (*FieldRef) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{134} + return file_common_proto_rawDescGZIP(), []int{135} } func (x *FieldRef) GetMetadata() string { @@ -10739,7 +10794,7 @@ type FieldCondition struct { func (x *FieldCondition) Reset() { *x = FieldCondition{} - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10751,7 +10806,7 @@ func (x *FieldCondition) String() string { func (*FieldCondition) ProtoMessage() {} func (x *FieldCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10764,7 +10819,7 @@ func (x *FieldCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldCondition.ProtoReflect.Descriptor instead. func (*FieldCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{135} + return file_common_proto_rawDescGZIP(), []int{136} } func (x *FieldCondition) GetField() *FieldRef { @@ -10873,7 +10928,7 @@ type StringCondition struct { func (x *StringCondition) Reset() { *x = StringCondition{} - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10885,7 +10940,7 @@ func (x *StringCondition) String() string { func (*StringCondition) ProtoMessage() {} func (x *StringCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10898,7 +10953,7 @@ func (x *StringCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use StringCondition.ProtoReflect.Descriptor instead. func (*StringCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{136} + return file_common_proto_rawDescGZIP(), []int{137} } func (x *StringCondition) GetValue() isStringCondition_Value { @@ -10956,7 +11011,7 @@ type IntCondition struct { func (x *IntCondition) Reset() { *x = IntCondition{} - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10968,7 +11023,7 @@ func (x *IntCondition) String() string { func (*IntCondition) ProtoMessage() {} func (x *IntCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10981,7 +11036,7 @@ func (x *IntCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use IntCondition.ProtoReflect.Descriptor instead. func (*IntCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{137} + return file_common_proto_rawDescGZIP(), []int{138} } func (x *IntCondition) GetMin() int64 { @@ -11040,7 +11095,7 @@ type UintCondition struct { func (x *UintCondition) Reset() { *x = UintCondition{} - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11052,7 +11107,7 @@ func (x *UintCondition) String() string { func (*UintCondition) ProtoMessage() {} func (x *UintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11065,7 +11120,7 @@ func (x *UintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use UintCondition.ProtoReflect.Descriptor instead. func (*UintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{138} + return file_common_proto_rawDescGZIP(), []int{139} } func (x *UintCondition) GetMin() uint64 { @@ -11123,7 +11178,7 @@ type BoolCondition struct { func (x *BoolCondition) Reset() { *x = BoolCondition{} - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11135,7 +11190,7 @@ func (x *BoolCondition) String() string { func (*BoolCondition) ProtoMessage() {} func (x *BoolCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11148,7 +11203,7 @@ func (x *BoolCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BoolCondition.ProtoReflect.Descriptor instead. func (*BoolCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{139} + return file_common_proto_rawDescGZIP(), []int{140} } func (x *BoolCondition) GetValue() isBoolCondition_Value { @@ -11201,7 +11256,7 @@ type ExistsCondition struct { func (x *ExistsCondition) Reset() { *x = ExistsCondition{} - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11213,7 +11268,7 @@ func (x *ExistsCondition) String() string { func (*ExistsCondition) ProtoMessage() {} func (x *ExistsCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11226,7 +11281,7 @@ func (x *ExistsCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use ExistsCondition.ProtoReflect.Descriptor instead. func (*ExistsCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{140} + return file_common_proto_rawDescGZIP(), []int{141} } func (x *ExistsCondition) GetIncludeNull() bool { @@ -11252,7 +11307,7 @@ type AddressMatch struct { func (x *AddressMatch) Reset() { *x = AddressMatch{} - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11264,7 +11319,7 @@ func (x *AddressMatch) String() string { func (*AddressMatch) ProtoMessage() {} func (x *AddressMatch) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11277,7 +11332,7 @@ func (x *AddressMatch) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressMatch.ProtoReflect.Descriptor instead. func (*AddressMatch) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{141} + return file_common_proto_rawDescGZIP(), []int{142} } func (x *AddressMatch) GetMatch() isAddressMatch_Match { @@ -11373,7 +11428,7 @@ type PreparedQuery struct { func (x *PreparedQuery) Reset() { *x = PreparedQuery{} - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11385,7 +11440,7 @@ func (x *PreparedQuery) String() string { func (*PreparedQuery) ProtoMessage() {} func (x *PreparedQuery) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11398,7 +11453,7 @@ func (x *PreparedQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQuery.ProtoReflect.Descriptor instead. func (*PreparedQuery) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{142} + return file_common_proto_rawDescGZIP(), []int{143} } func (x *PreparedQuery) GetName() string { @@ -11434,7 +11489,7 @@ type AggregatedVolume struct { func (x *AggregatedVolume) Reset() { *x = AggregatedVolume{} - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11446,7 +11501,7 @@ func (x *AggregatedVolume) String() string { func (*AggregatedVolume) ProtoMessage() {} func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11459,7 +11514,7 @@ func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregatedVolume.ProtoReflect.Descriptor instead. func (*AggregatedVolume) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{143} + return file_common_proto_rawDescGZIP(), []int{144} } func (x *AggregatedVolume) GetAsset() string { @@ -11494,7 +11549,7 @@ type AggregateResult struct { func (x *AggregateResult) Reset() { *x = AggregateResult{} - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11506,7 +11561,7 @@ func (x *AggregateResult) String() string { func (*AggregateResult) ProtoMessage() {} func (x *AggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11519,7 +11574,7 @@ func (x *AggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregateResult.ProtoReflect.Descriptor instead. func (*AggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{144} + return file_common_proto_rawDescGZIP(), []int{145} } func (x *AggregateResult) GetVolumes() []*AggregatedVolume { @@ -11547,7 +11602,7 @@ type GroupedAggregateResult struct { func (x *GroupedAggregateResult) Reset() { *x = GroupedAggregateResult{} - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11559,7 +11614,7 @@ func (x *GroupedAggregateResult) String() string { func (*GroupedAggregateResult) ProtoMessage() {} func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11572,7 +11627,7 @@ func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use GroupedAggregateResult.ProtoReflect.Descriptor instead. func (*GroupedAggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{145} + return file_common_proto_rawDescGZIP(), []int{146} } func (x *GroupedAggregateResult) GetPrefix() string { @@ -11604,7 +11659,7 @@ type PreparedQueryCursor struct { func (x *PreparedQueryCursor) Reset() { *x = PreparedQueryCursor{} - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11616,7 +11671,7 @@ func (x *PreparedQueryCursor) String() string { func (*PreparedQueryCursor) ProtoMessage() {} func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11629,7 +11684,7 @@ func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQueryCursor.ProtoReflect.Descriptor instead. func (*PreparedQueryCursor) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{146} + return file_common_proto_rawDescGZIP(), []int{147} } func (x *PreparedQueryCursor) GetPageSize() uint32 { @@ -11693,7 +11748,7 @@ type LedgerStats struct { func (x *LedgerStats) Reset() { *x = LedgerStats{} - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11705,7 +11760,7 @@ func (x *LedgerStats) String() string { func (*LedgerStats) ProtoMessage() {} func (x *LedgerStats) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11718,7 +11773,7 @@ func (x *LedgerStats) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerStats.ProtoReflect.Descriptor instead. func (*LedgerStats) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{147} + return file_common_proto_rawDescGZIP(), []int{148} } func (x *LedgerStats) GetTransactionCount() uint64 { @@ -11806,7 +11861,7 @@ type PersistedConfig struct { func (x *PersistedConfig) Reset() { *x = PersistedConfig{} - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11818,7 +11873,7 @@ func (x *PersistedConfig) String() string { func (*PersistedConfig) ProtoMessage() {} func (x *PersistedConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11831,7 +11886,7 @@ func (x *PersistedConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PersistedConfig.ProtoReflect.Descriptor instead. func (*PersistedConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{148} + return file_common_proto_rawDescGZIP(), []int{149} } func (x *PersistedConfig) GetNodeId() uint64 { @@ -11889,7 +11944,7 @@ type CallerIdentity struct { func (x *CallerIdentity) Reset() { *x = CallerIdentity{} - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11901,7 +11956,7 @@ func (x *CallerIdentity) String() string { func (*CallerIdentity) ProtoMessage() {} func (x *CallerIdentity) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11914,7 +11969,7 @@ func (x *CallerIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerIdentity.ProtoReflect.Descriptor instead. func (*CallerIdentity) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{149} + return file_common_proto_rawDescGZIP(), []int{150} } func (x *CallerIdentity) GetSubject() string { @@ -12001,7 +12056,7 @@ type CallerSnapshot struct { func (x *CallerSnapshot) Reset() { *x = CallerSnapshot{} - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12013,7 +12068,7 @@ func (x *CallerSnapshot) String() string { func (*CallerSnapshot) ProtoMessage() {} func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12026,7 +12081,7 @@ func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerSnapshot.ProtoReflect.Descriptor instead. func (*CallerSnapshot) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{150} + return file_common_proto_rawDescGZIP(), []int{151} } func (x *CallerSnapshot) GetIdentity() *CallerIdentity { @@ -12064,7 +12119,7 @@ type S3StorageConfig struct { func (x *S3StorageConfig) Reset() { *x = S3StorageConfig{} - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12076,7 +12131,7 @@ func (x *S3StorageConfig) String() string { func (*S3StorageConfig) ProtoMessage() {} func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12089,7 +12144,7 @@ func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use S3StorageConfig.ProtoReflect.Descriptor instead. func (*S3StorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{151} + return file_common_proto_rawDescGZIP(), []int{152} } func (x *S3StorageConfig) GetBucket() string { @@ -12140,7 +12195,7 @@ type AzureStorageConfig struct { func (x *AzureStorageConfig) Reset() { *x = AzureStorageConfig{} - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12152,7 +12207,7 @@ func (x *AzureStorageConfig) String() string { func (*AzureStorageConfig) ProtoMessage() {} func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12165,7 +12220,7 @@ func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureStorageConfig.ProtoReflect.Descriptor instead. func (*AzureStorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{152} + return file_common_proto_rawDescGZIP(), []int{153} } func (x *AzureStorageConfig) GetAccountName() string { @@ -12212,7 +12267,7 @@ type BackupStorage struct { func (x *BackupStorage) Reset() { *x = BackupStorage{} - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12224,7 +12279,7 @@ func (x *BackupStorage) String() string { func (*BackupStorage) ProtoMessage() {} func (x *BackupStorage) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12237,7 +12292,7 @@ func (x *BackupStorage) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupStorage.ProtoReflect.Descriptor instead. func (*BackupStorage) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{153} + return file_common_proto_rawDescGZIP(), []int{154} } func (x *BackupStorage) GetProvider() isBackupStorage_Provider { @@ -12300,7 +12355,7 @@ type ReadOptions struct { func (x *ReadOptions) Reset() { *x = ReadOptions{} - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12312,7 +12367,7 @@ func (x *ReadOptions) String() string { func (*ReadOptions) ProtoMessage() {} func (x *ReadOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12325,7 +12380,7 @@ func (x *ReadOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadOptions.ProtoReflect.Descriptor instead. func (*ReadOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{154} + return file_common_proto_rawDescGZIP(), []int{155} } func (x *ReadOptions) GetCheckpointId() uint64 { @@ -12377,7 +12432,7 @@ type ListOptions struct { func (x *ListOptions) Reset() { *x = ListOptions{} - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12389,7 +12444,7 @@ func (x *ListOptions) String() string { func (*ListOptions) ProtoMessage() {} func (x *ListOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12402,7 +12457,7 @@ func (x *ListOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOptions.ProtoReflect.Descriptor instead. func (*ListOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{155} + return file_common_proto_rawDescGZIP(), []int{156} } func (x *ListOptions) GetRead() *ReadOptions { @@ -12712,7 +12767,10 @@ const file_common_proto_rawDesc = "" + "\x04info\x18\x01 \x01(\v2\x15.common.NumscriptInfoR\x04info\"A\n" + "\x13DeletedNumscriptLog\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + - "\x06ledger\x18\x02 \x01(\tR\x06ledger\"3\n" + + "\x06ledger\x18\x02 \x01(\tR\x06ledger\"U\n" + + "\rTemplateUsage\x12\x14\n" + + "\x05count\x18\x01 \x01(\x06R\x05count\x12.\n" + + "\tlast_used\x18\x02 \x01(\v2\x11.common.TimestampR\blastUsed\"3\n" + "\x1dSetQueryCheckpointScheduleLog\x12\x12\n" + "\x04cron\x18\x01 \x01(\tR\x04cron\"#\n" + "!DeletedQueryCheckpointScheduleLog\"c\n" + @@ -13431,7 +13489,7 @@ func file_common_proto_rawDescGZIP() []byte { } var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 18) -var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 176) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 177) var file_common_proto_goTypes = []any{ (TargetType)(0), // 0: common.TargetType (MetadataType)(0), // 1: common.MetadataType @@ -13498,160 +13556,161 @@ var file_common_proto_goTypes = []any{ (*NumscriptInfo)(nil), // 62: common.NumscriptInfo (*SavedNumscriptLog)(nil), // 63: common.SavedNumscriptLog (*DeletedNumscriptLog)(nil), // 64: common.DeletedNumscriptLog - (*SetQueryCheckpointScheduleLog)(nil), // 65: common.SetQueryCheckpointScheduleLog - (*DeletedQueryCheckpointScheduleLog)(nil), // 66: common.DeletedQueryCheckpointScheduleLog - (*CreatedQueryCheckpointLog)(nil), // 67: common.CreatedQueryCheckpointLog - (*DeletedQueryCheckpointLog)(nil), // 68: common.DeletedQueryCheckpointLog - (*SinkConfig)(nil), // 69: common.SinkConfig - (*SinkStatus)(nil), // 70: common.SinkStatus - (*SinkError)(nil), // 71: common.SinkError - (*NatsSinkConfig)(nil), // 72: common.NatsSinkConfig - (*ClickHouseSinkConfig)(nil), // 73: common.ClickHouseSinkConfig - (*KafkaSinkConfig)(nil), // 74: common.KafkaSinkConfig - (*HttpSinkConfig)(nil), // 75: common.HttpSinkConfig - (*DatabricksSinkConfig)(nil), // 76: common.DatabricksSinkConfig - (*DatabricksOAuthM2M)(nil), // 77: common.DatabricksOAuthM2M - (*CreatedLedgerLog)(nil), // 78: common.CreatedLedgerLog - (*DeletedLedgerLog)(nil), // 79: common.DeletedLedgerLog - (*ApplyLedgerLog)(nil), // 80: common.ApplyLedgerLog - (*LedgerLog)(nil), // 81: common.LedgerLog - (*TouchedVolume)(nil), // 82: common.TouchedVolume - (*LedgerLogPayload)(nil), // 83: common.LedgerLogPayload - (*CreatedIndexLog)(nil), // 84: common.CreatedIndexLog - (*DroppedIndexLog)(nil), // 85: common.DroppedIndexLog - (*FilledGapLog)(nil), // 86: common.FilledGapLog - (*CreatedTransaction)(nil), // 87: common.CreatedTransaction - (*RevertedTransaction)(nil), // 88: common.RevertedTransaction - (*SavedMetadata)(nil), // 89: common.SavedMetadata - (*DeletedMetadata)(nil), // 90: common.DeletedMetadata - (*SetMetadataFieldTypeLog)(nil), // 91: common.SetMetadataFieldTypeLog - (*RemovedMetadataFieldTypeLog)(nil), // 92: common.RemovedMetadataFieldTypeLog - (*Chapter)(nil), // 93: common.Chapter - (*ClosedChapterLog)(nil), // 94: common.ClosedChapterLog - (*SealedChapterLog)(nil), // 95: common.SealedChapterLog - (*ArchivedChapterLog)(nil), // 96: common.ArchivedChapterLog - (*ConfirmedArchiveChapterLog)(nil), // 97: common.ConfirmedArchiveChapterLog - (*MirrorSourceConfig)(nil), // 98: common.MirrorSourceConfig - (*MirrorRewriteRule)(nil), // 99: common.MirrorRewriteRule - (*CreatedTransactionRule)(nil), // 100: common.CreatedTransactionRule - (*RevertedTransactionRule)(nil), // 101: common.RevertedTransactionRule - (*SavedMetadataRule)(nil), // 102: common.SavedMetadataRule - (*DeletedMetadataRule)(nil), // 103: common.DeletedMetadataRule - (*AnyVariantRule)(nil), // 104: common.AnyVariantRule - (*CreatedTransactionAction)(nil), // 105: common.CreatedTransactionAction - (*RevertedTransactionAction)(nil), // 106: common.RevertedTransactionAction - (*SavedMetadataAction)(nil), // 107: common.SavedMetadataAction - (*DeletedMetadataAction)(nil), // 108: common.DeletedMetadataAction - (*AnyVariantAction)(nil), // 109: common.AnyVariantAction - (*RewriteAddressAction)(nil), // 110: common.RewriteAddressAction - (*SetMetadataAction)(nil), // 111: common.SetMetadataAction - (*DeleteMetadataAction)(nil), // 112: common.DeleteMetadataAction - (*SetAccountMetadataAction)(nil), // 113: common.SetAccountMetadataAction - (*DeleteAccountMetadataAction)(nil), // 114: common.DeleteAccountMetadataAction - (*SetAccountMetadataFromAddressAction)(nil), // 115: common.SetAccountMetadataFromAddressAction - (*SetAccountMetadataFromAddressReplacement)(nil), // 116: common.SetAccountMetadataFromAddressReplacement - (*DropAction)(nil), // 117: common.DropAction - (*HttpMirrorSourceConfig)(nil), // 118: common.HttpMirrorSourceConfig - (*OAuth2ClientCredentials)(nil), // 119: common.OAuth2ClientCredentials - (*PostgresMirrorSourceConfig)(nil), // 120: common.PostgresMirrorSourceConfig - (*PostgresAwsIamAuth)(nil), // 121: common.PostgresAwsIamAuth - (*MirrorSyncError)(nil), // 122: common.MirrorSyncError - (*MirrorSyncProgress)(nil), // 123: common.MirrorSyncProgress - (*LedgerInfo)(nil), // 124: common.LedgerInfo - (*SaveMetadataCommand)(nil), // 125: common.SaveMetadataCommand - (*DeleteMetadataCommand)(nil), // 126: common.DeleteMetadataCommand - (*TransactionState)(nil), // 127: common.TransactionState - (*IdempotencyKeyValue)(nil), // 128: common.IdempotencyKeyValue - (*IdempotencyFailure)(nil), // 129: common.IdempotencyFailure - (*TransactionReferenceValue)(nil), // 130: common.TransactionReferenceValue - (*NumscriptVersionValue)(nil), // 131: common.NumscriptVersionValue - (*SegmentType)(nil), // 132: common.SegmentType - (*UUIDConstraint)(nil), // 133: common.UUIDConstraint - (*Uint64Constraint)(nil), // 134: common.Uint64Constraint - (*BytesConstraint)(nil), // 135: common.BytesConstraint - (*AccountType)(nil), // 136: common.AccountType - (*AddedAccountTypeLog)(nil), // 137: common.AddedAccountTypeLog - (*RemovedAccountTypeLog)(nil), // 138: common.RemovedAccountTypeLog - (*UpdatedDefaultEnforcementModeLog)(nil), // 139: common.UpdatedDefaultEnforcementModeLog - (*QueryFilter)(nil), // 140: common.QueryFilter - (*ReferenceCondition)(nil), // 141: common.ReferenceCondition - (*RevertedCondition)(nil), // 142: common.RevertedCondition - (*AuditCondition)(nil), // 143: common.AuditCondition - (*LedgerCondition)(nil), // 144: common.LedgerCondition - (*LogIdCondition)(nil), // 145: common.LogIdCondition - (*BuiltinUintCondition)(nil), // 146: common.BuiltinUintCondition - (*LogBuiltinUintCondition)(nil), // 147: common.LogBuiltinUintCondition - (*AccountHasAssetCondition)(nil), // 148: common.AccountHasAssetCondition - (*AndFilter)(nil), // 149: common.AndFilter - (*OrFilter)(nil), // 150: common.OrFilter - (*NotFilter)(nil), // 151: common.NotFilter - (*FieldRef)(nil), // 152: common.FieldRef - (*FieldCondition)(nil), // 153: common.FieldCondition - (*StringCondition)(nil), // 154: common.StringCondition - (*IntCondition)(nil), // 155: common.IntCondition - (*UintCondition)(nil), // 156: common.UintCondition - (*BoolCondition)(nil), // 157: common.BoolCondition - (*ExistsCondition)(nil), // 158: common.ExistsCondition - (*AddressMatch)(nil), // 159: common.AddressMatch - (*PreparedQuery)(nil), // 160: common.PreparedQuery - (*AggregatedVolume)(nil), // 161: common.AggregatedVolume - (*AggregateResult)(nil), // 162: common.AggregateResult - (*GroupedAggregateResult)(nil), // 163: common.GroupedAggregateResult - (*PreparedQueryCursor)(nil), // 164: common.PreparedQueryCursor - (*LedgerStats)(nil), // 165: common.LedgerStats - (*PersistedConfig)(nil), // 166: common.PersistedConfig - (*CallerIdentity)(nil), // 167: common.CallerIdentity - (*CallerSnapshot)(nil), // 168: common.CallerSnapshot - (*S3StorageConfig)(nil), // 169: common.S3StorageConfig - (*AzureStorageConfig)(nil), // 170: common.AzureStorageConfig - (*BackupStorage)(nil), // 171: common.BackupStorage - (*ReadOptions)(nil), // 172: common.ReadOptions - (*ListOptions)(nil), // 173: common.ListOptions - nil, // 174: common.MetadataMap.ValuesEntry - nil, // 175: common.Transaction.MetadataEntry - nil, // 176: common.Script.VarsEntry - nil, // 177: common.VolumesByAssets.VolumesEntry - nil, // 178: common.PostCommitVolumes.VolumesByAccountEntry - nil, // 179: common.Account.MetadataEntry - nil, // 180: common.Account.VolumesEntry - nil, // 181: common.MetadataSchema.AccountFieldsEntry - nil, // 182: common.MetadataSchema.TransactionFieldsEntry - nil, // 183: common.MetadataSchema.LedgerFieldsEntry - nil, // 184: common.SavedLedgerMetadataLog.MetadataEntry - nil, // 185: common.CreatedLedgerLog.AccountTypesEntry - nil, // 186: common.CreatedTransaction.AccountMetadataEntry - nil, // 187: common.SavedMetadata.MetadataEntry - nil, // 188: common.LedgerInfo.AccountTypesEntry - nil, // 189: common.LedgerInfo.MetadataEntry - nil, // 190: common.SaveMetadataCommand.MetadataEntry - nil, // 191: common.TransactionState.MetadataEntry - nil, // 192: common.IdempotencyFailure.MetadataEntry - nil, // 193: common.AccountType.SegmentTypesEntry - (*signaturepb.SignedLog)(nil), // 194: signature.SignedLog + (*TemplateUsage)(nil), // 65: common.TemplateUsage + (*SetQueryCheckpointScheduleLog)(nil), // 66: common.SetQueryCheckpointScheduleLog + (*DeletedQueryCheckpointScheduleLog)(nil), // 67: common.DeletedQueryCheckpointScheduleLog + (*CreatedQueryCheckpointLog)(nil), // 68: common.CreatedQueryCheckpointLog + (*DeletedQueryCheckpointLog)(nil), // 69: common.DeletedQueryCheckpointLog + (*SinkConfig)(nil), // 70: common.SinkConfig + (*SinkStatus)(nil), // 71: common.SinkStatus + (*SinkError)(nil), // 72: common.SinkError + (*NatsSinkConfig)(nil), // 73: common.NatsSinkConfig + (*ClickHouseSinkConfig)(nil), // 74: common.ClickHouseSinkConfig + (*KafkaSinkConfig)(nil), // 75: common.KafkaSinkConfig + (*HttpSinkConfig)(nil), // 76: common.HttpSinkConfig + (*DatabricksSinkConfig)(nil), // 77: common.DatabricksSinkConfig + (*DatabricksOAuthM2M)(nil), // 78: common.DatabricksOAuthM2M + (*CreatedLedgerLog)(nil), // 79: common.CreatedLedgerLog + (*DeletedLedgerLog)(nil), // 80: common.DeletedLedgerLog + (*ApplyLedgerLog)(nil), // 81: common.ApplyLedgerLog + (*LedgerLog)(nil), // 82: common.LedgerLog + (*TouchedVolume)(nil), // 83: common.TouchedVolume + (*LedgerLogPayload)(nil), // 84: common.LedgerLogPayload + (*CreatedIndexLog)(nil), // 85: common.CreatedIndexLog + (*DroppedIndexLog)(nil), // 86: common.DroppedIndexLog + (*FilledGapLog)(nil), // 87: common.FilledGapLog + (*CreatedTransaction)(nil), // 88: common.CreatedTransaction + (*RevertedTransaction)(nil), // 89: common.RevertedTransaction + (*SavedMetadata)(nil), // 90: common.SavedMetadata + (*DeletedMetadata)(nil), // 91: common.DeletedMetadata + (*SetMetadataFieldTypeLog)(nil), // 92: common.SetMetadataFieldTypeLog + (*RemovedMetadataFieldTypeLog)(nil), // 93: common.RemovedMetadataFieldTypeLog + (*Chapter)(nil), // 94: common.Chapter + (*ClosedChapterLog)(nil), // 95: common.ClosedChapterLog + (*SealedChapterLog)(nil), // 96: common.SealedChapterLog + (*ArchivedChapterLog)(nil), // 97: common.ArchivedChapterLog + (*ConfirmedArchiveChapterLog)(nil), // 98: common.ConfirmedArchiveChapterLog + (*MirrorSourceConfig)(nil), // 99: common.MirrorSourceConfig + (*MirrorRewriteRule)(nil), // 100: common.MirrorRewriteRule + (*CreatedTransactionRule)(nil), // 101: common.CreatedTransactionRule + (*RevertedTransactionRule)(nil), // 102: common.RevertedTransactionRule + (*SavedMetadataRule)(nil), // 103: common.SavedMetadataRule + (*DeletedMetadataRule)(nil), // 104: common.DeletedMetadataRule + (*AnyVariantRule)(nil), // 105: common.AnyVariantRule + (*CreatedTransactionAction)(nil), // 106: common.CreatedTransactionAction + (*RevertedTransactionAction)(nil), // 107: common.RevertedTransactionAction + (*SavedMetadataAction)(nil), // 108: common.SavedMetadataAction + (*DeletedMetadataAction)(nil), // 109: common.DeletedMetadataAction + (*AnyVariantAction)(nil), // 110: common.AnyVariantAction + (*RewriteAddressAction)(nil), // 111: common.RewriteAddressAction + (*SetMetadataAction)(nil), // 112: common.SetMetadataAction + (*DeleteMetadataAction)(nil), // 113: common.DeleteMetadataAction + (*SetAccountMetadataAction)(nil), // 114: common.SetAccountMetadataAction + (*DeleteAccountMetadataAction)(nil), // 115: common.DeleteAccountMetadataAction + (*SetAccountMetadataFromAddressAction)(nil), // 116: common.SetAccountMetadataFromAddressAction + (*SetAccountMetadataFromAddressReplacement)(nil), // 117: common.SetAccountMetadataFromAddressReplacement + (*DropAction)(nil), // 118: common.DropAction + (*HttpMirrorSourceConfig)(nil), // 119: common.HttpMirrorSourceConfig + (*OAuth2ClientCredentials)(nil), // 120: common.OAuth2ClientCredentials + (*PostgresMirrorSourceConfig)(nil), // 121: common.PostgresMirrorSourceConfig + (*PostgresAwsIamAuth)(nil), // 122: common.PostgresAwsIamAuth + (*MirrorSyncError)(nil), // 123: common.MirrorSyncError + (*MirrorSyncProgress)(nil), // 124: common.MirrorSyncProgress + (*LedgerInfo)(nil), // 125: common.LedgerInfo + (*SaveMetadataCommand)(nil), // 126: common.SaveMetadataCommand + (*DeleteMetadataCommand)(nil), // 127: common.DeleteMetadataCommand + (*TransactionState)(nil), // 128: common.TransactionState + (*IdempotencyKeyValue)(nil), // 129: common.IdempotencyKeyValue + (*IdempotencyFailure)(nil), // 130: common.IdempotencyFailure + (*TransactionReferenceValue)(nil), // 131: common.TransactionReferenceValue + (*NumscriptVersionValue)(nil), // 132: common.NumscriptVersionValue + (*SegmentType)(nil), // 133: common.SegmentType + (*UUIDConstraint)(nil), // 134: common.UUIDConstraint + (*Uint64Constraint)(nil), // 135: common.Uint64Constraint + (*BytesConstraint)(nil), // 136: common.BytesConstraint + (*AccountType)(nil), // 137: common.AccountType + (*AddedAccountTypeLog)(nil), // 138: common.AddedAccountTypeLog + (*RemovedAccountTypeLog)(nil), // 139: common.RemovedAccountTypeLog + (*UpdatedDefaultEnforcementModeLog)(nil), // 140: common.UpdatedDefaultEnforcementModeLog + (*QueryFilter)(nil), // 141: common.QueryFilter + (*ReferenceCondition)(nil), // 142: common.ReferenceCondition + (*RevertedCondition)(nil), // 143: common.RevertedCondition + (*AuditCondition)(nil), // 144: common.AuditCondition + (*LedgerCondition)(nil), // 145: common.LedgerCondition + (*LogIdCondition)(nil), // 146: common.LogIdCondition + (*BuiltinUintCondition)(nil), // 147: common.BuiltinUintCondition + (*LogBuiltinUintCondition)(nil), // 148: common.LogBuiltinUintCondition + (*AccountHasAssetCondition)(nil), // 149: common.AccountHasAssetCondition + (*AndFilter)(nil), // 150: common.AndFilter + (*OrFilter)(nil), // 151: common.OrFilter + (*NotFilter)(nil), // 152: common.NotFilter + (*FieldRef)(nil), // 153: common.FieldRef + (*FieldCondition)(nil), // 154: common.FieldCondition + (*StringCondition)(nil), // 155: common.StringCondition + (*IntCondition)(nil), // 156: common.IntCondition + (*UintCondition)(nil), // 157: common.UintCondition + (*BoolCondition)(nil), // 158: common.BoolCondition + (*ExistsCondition)(nil), // 159: common.ExistsCondition + (*AddressMatch)(nil), // 160: common.AddressMatch + (*PreparedQuery)(nil), // 161: common.PreparedQuery + (*AggregatedVolume)(nil), // 162: common.AggregatedVolume + (*AggregateResult)(nil), // 163: common.AggregateResult + (*GroupedAggregateResult)(nil), // 164: common.GroupedAggregateResult + (*PreparedQueryCursor)(nil), // 165: common.PreparedQueryCursor + (*LedgerStats)(nil), // 166: common.LedgerStats + (*PersistedConfig)(nil), // 167: common.PersistedConfig + (*CallerIdentity)(nil), // 168: common.CallerIdentity + (*CallerSnapshot)(nil), // 169: common.CallerSnapshot + (*S3StorageConfig)(nil), // 170: common.S3StorageConfig + (*AzureStorageConfig)(nil), // 171: common.AzureStorageConfig + (*BackupStorage)(nil), // 172: common.BackupStorage + (*ReadOptions)(nil), // 173: common.ReadOptions + (*ListOptions)(nil), // 174: common.ListOptions + nil, // 175: common.MetadataMap.ValuesEntry + nil, // 176: common.Transaction.MetadataEntry + nil, // 177: common.Script.VarsEntry + nil, // 178: common.VolumesByAssets.VolumesEntry + nil, // 179: common.PostCommitVolumes.VolumesByAccountEntry + nil, // 180: common.Account.MetadataEntry + nil, // 181: common.Account.VolumesEntry + nil, // 182: common.MetadataSchema.AccountFieldsEntry + nil, // 183: common.MetadataSchema.TransactionFieldsEntry + nil, // 184: common.MetadataSchema.LedgerFieldsEntry + nil, // 185: common.SavedLedgerMetadataLog.MetadataEntry + nil, // 186: common.CreatedLedgerLog.AccountTypesEntry + nil, // 187: common.CreatedTransaction.AccountMetadataEntry + nil, // 188: common.SavedMetadata.MetadataEntry + nil, // 189: common.LedgerInfo.AccountTypesEntry + nil, // 190: common.LedgerInfo.MetadataEntry + nil, // 191: common.SaveMetadataCommand.MetadataEntry + nil, // 192: common.TransactionState.MetadataEntry + nil, // 193: common.IdempotencyFailure.MetadataEntry + nil, // 194: common.AccountType.SegmentTypesEntry + (*signaturepb.SignedLog)(nil), // 195: signature.SignedLog } var file_common_proto_depIdxs = []int32{ 19, // 0: common.MetadataValue.null_value:type_name -> common.NullValue - 174, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry + 175, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry 23, // 2: common.Posting.amount:type_name -> common.Uint256 24, // 3: common.Transaction.postings:type_name -> common.Posting - 175, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry + 176, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry 18, // 5: common.Transaction.timestamp:type_name -> common.Timestamp 18, // 6: common.Transaction.inserted_at:type_name -> common.Timestamp 18, // 7: common.Transaction.updated_at:type_name -> common.Timestamp 18, // 8: common.Transaction.reverted_at:type_name -> common.Timestamp - 176, // 9: common.Script.vars:type_name -> common.Script.VarsEntry - 177, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry - 178, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry - 179, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry + 177, // 9: common.Script.vars:type_name -> common.Script.VarsEntry + 178, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry + 179, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry + 180, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry 18, // 13: common.Account.first_usage:type_name -> common.Timestamp 18, // 14: common.Account.insertion_date:type_name -> common.Timestamp 18, // 15: common.Account.updated_at:type_name -> common.Timestamp - 180, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry + 181, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry 32, // 17: common.Target.account:type_name -> common.TargetAccount 1, // 18: common.MetadataFieldSchema.type:type_name -> common.MetadataType - 181, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry - 182, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry - 183, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry + 182, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry + 183, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry + 184, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry 0, // 22: common.SetMetadataFieldTypeCommand.target_type:type_name -> common.TargetType 1, // 23: common.SetMetadataFieldTypeCommand.type:type_name -> common.MetadataType 0, // 24: common.MetadataIndexID.target:type_name -> common.TargetType @@ -13664,19 +13723,19 @@ var file_common_proto_depIdxs = []int32{ 18, // 31: common.Index.created_at:type_name -> common.Timestamp 18, // 32: common.Index.last_built_at:type_name -> common.Timestamp 43, // 33: common.Log.payload:type_name -> common.LogPayload - 194, // 34: common.Log.response_signature:type_name -> signature.SignedLog - 78, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog - 79, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog - 80, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog + 195, // 34: common.Log.response_signature:type_name -> signature.SignedLog + 79, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog + 80, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog + 81, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog 45, // 38: common.LogPayload.register_signing_key:type_name -> common.RegisteredSigningKeyLog 46, // 39: common.LogPayload.revoke_signing_key:type_name -> common.RevokedSigningKeyLog 48, // 40: common.LogPayload.set_signing_config:type_name -> common.SetSigningConfigLog 49, // 41: common.LogPayload.added_events_sink:type_name -> common.AddedEventsSinkLog 50, // 42: common.LogPayload.removed_events_sink:type_name -> common.RemovedEventsSinkLog - 94, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog - 95, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog - 96, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog - 97, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog + 95, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog + 96, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog + 97, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog + 98, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog 51, // 47: common.LogPayload.set_maintenance_mode:type_name -> common.SetMaintenanceModeLog 55, // 48: common.LogPayload.set_chapter_schedule:type_name -> common.SetChapterScheduleLog 56, // 49: common.LogPayload.delete_chapter_schedule:type_name -> common.DeletedChapterScheduleLog @@ -13686,13 +13745,13 @@ var file_common_proto_depIdxs = []int32{ 59, // 53: common.LogPayload.deleted_prepared_query:type_name -> common.DeletedPreparedQueryLog 63, // 54: common.LogPayload.saved_numscript:type_name -> common.SavedNumscriptLog 64, // 55: common.LogPayload.deleted_numscript:type_name -> common.DeletedNumscriptLog - 67, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog - 68, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog - 65, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog - 66, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog + 68, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog + 69, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog + 66, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog + 67, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog 60, // 60: common.LogPayload.saved_ledger_metadata:type_name -> common.SavedLedgerMetadataLog 61, // 61: common.LogPayload.deleted_ledger_metadata:type_name -> common.DeletedLedgerMetadataLog - 69, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig + 70, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig 52, // 63: common.ClusterConfig.bloom_volumes:type_name -> common.BloomTypeConfig 52, // 64: common.ClusterConfig.bloom_metadata:type_name -> common.BloomTypeConfig 52, // 65: common.ClusterConfig.bloom_references:type_name -> common.BloomTypeConfig @@ -13707,200 +13766,201 @@ var file_common_proto_depIdxs = []int32{ 52, // 74: common.ClusterConfig.bloom_prepared_queries:type_name -> common.BloomTypeConfig 52, // 75: common.ClusterConfig.bloom_indexes:type_name -> common.BloomTypeConfig 53, // 76: common.PersistedClusterState.config:type_name -> common.ClusterConfig - 160, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery - 140, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter - 140, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter - 184, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry + 161, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery + 141, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter + 141, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter + 185, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry 18, // 81: common.NumscriptInfo.created_at:type_name -> common.Timestamp 62, // 82: common.SavedNumscriptLog.info:type_name -> common.NumscriptInfo - 72, // 83: common.SinkConfig.nats:type_name -> common.NatsSinkConfig - 73, // 84: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig - 74, // 85: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig - 75, // 86: common.SinkConfig.http:type_name -> common.HttpSinkConfig - 76, // 87: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig - 7, // 88: common.SinkConfig.event_types:type_name -> common.EventType - 71, // 89: common.SinkStatus.error:type_name -> common.SinkError - 18, // 90: common.SinkError.occurred_at:type_name -> common.Timestamp - 77, // 91: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M - 18, // 92: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp - 35, // 93: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema - 9, // 94: common.CreatedLedgerLog.mode:type_name -> common.LedgerMode - 98, // 95: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig - 185, // 96: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry - 12, // 97: common.CreatedLedgerLog.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 18, // 98: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp - 81, // 99: common.ApplyLedgerLog.log:type_name -> common.LedgerLog - 83, // 100: common.LedgerLog.data:type_name -> common.LedgerLogPayload - 18, // 101: common.LedgerLog.date:type_name -> common.Timestamp - 82, // 102: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume - 87, // 103: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction - 88, // 104: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction - 89, // 105: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata - 90, // 106: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata - 91, // 107: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog - 92, // 108: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog - 86, // 109: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog - 84, // 110: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog - 85, // 111: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog - 137, // 112: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog - 138, // 113: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog - 139, // 114: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog - 38, // 115: common.CreatedIndexLog.id:type_name -> common.IndexID - 38, // 116: common.DroppedIndexLog.id:type_name -> common.IndexID - 25, // 117: common.CreatedTransaction.transaction:type_name -> common.Transaction - 186, // 118: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry - 30, // 119: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 25, // 120: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction - 30, // 121: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 33, // 122: common.SavedMetadata.target:type_name -> common.Target - 187, // 123: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry - 33, // 124: common.DeletedMetadata.target:type_name -> common.Target - 0, // 125: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 1, // 126: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType - 0, // 127: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 38, // 128: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID - 18, // 129: common.Chapter.start:type_name -> common.Timestamp - 18, // 130: common.Chapter.end:type_name -> common.Timestamp - 8, // 131: common.Chapter.status:type_name -> common.ChapterStatus - 93, // 132: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter - 93, // 133: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter - 93, // 134: common.SealedChapterLog.chapter:type_name -> common.Chapter - 93, // 135: common.ArchivedChapterLog.chapter:type_name -> common.Chapter - 93, // 136: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter - 118, // 137: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig - 120, // 138: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig - 99, // 139: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule - 100, // 140: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule - 101, // 141: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule - 102, // 142: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule - 103, // 143: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule - 104, // 144: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule - 105, // 145: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction - 106, // 146: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction - 107, // 147: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction - 108, // 148: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction - 109, // 149: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction - 110, // 150: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 151: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 152: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 113, // 153: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction - 114, // 154: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction - 115, // 155: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction - 117, // 156: common.CreatedTransactionAction.drop:type_name -> common.DropAction - 110, // 157: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 158: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 159: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 117, // 160: common.RevertedTransactionAction.drop:type_name -> common.DropAction - 110, // 161: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 162: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 163: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction - 117, // 164: common.SavedMetadataAction.drop:type_name -> common.DropAction - 110, // 165: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 117, // 166: common.DeletedMetadataAction.drop:type_name -> common.DropAction - 110, // 167: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction - 117, // 168: common.AnyVariantAction.drop:type_name -> common.DropAction - 116, // 169: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement - 119, // 170: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials - 121, // 171: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth - 18, // 172: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp - 10, // 173: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState - 122, // 174: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError - 18, // 175: common.LedgerInfo.created_at:type_name -> common.Timestamp - 18, // 176: common.LedgerInfo.deleted_at:type_name -> common.Timestamp - 35, // 177: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema - 9, // 178: common.LedgerInfo.mode:type_name -> common.LedgerMode - 98, // 179: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig - 123, // 180: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress - 188, // 181: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry - 12, // 182: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 189, // 183: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry - 33, // 184: common.SaveMetadataCommand.target:type_name -> common.Target - 190, // 185: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry - 33, // 186: common.DeleteMetadataCommand.target:type_name -> common.Target - 191, // 187: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry - 18, // 188: common.TransactionState.timestamp:type_name -> common.Timestamp - 24, // 189: common.TransactionState.postings:type_name -> common.Posting - 18, // 190: common.TransactionState.reverted_at:type_name -> common.Timestamp - 129, // 191: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure - 11, // 192: common.IdempotencyFailure.reason:type_name -> common.ErrorReason - 192, // 193: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry - 133, // 194: common.SegmentType.uuid:type_name -> common.UUIDConstraint - 134, // 195: common.SegmentType.uint64:type_name -> common.Uint64Constraint - 135, // 196: common.SegmentType.bytes:type_name -> common.BytesConstraint - 13, // 197: common.AccountType.persistence:type_name -> common.AccountTypePersistence - 193, // 198: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry - 136, // 199: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType - 12, // 200: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode - 153, // 201: common.QueryFilter.field:type_name -> common.FieldCondition - 159, // 202: common.QueryFilter.address:type_name -> common.AddressMatch - 149, // 203: common.QueryFilter.and:type_name -> common.AndFilter - 150, // 204: common.QueryFilter.or:type_name -> common.OrFilter - 151, // 205: common.QueryFilter.not:type_name -> common.NotFilter - 141, // 206: common.QueryFilter.reference:type_name -> common.ReferenceCondition - 146, // 207: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition - 144, // 208: common.QueryFilter.ledger:type_name -> common.LedgerCondition - 145, // 209: common.QueryFilter.log_id:type_name -> common.LogIdCondition - 147, // 210: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition - 148, // 211: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition - 142, // 212: common.QueryFilter.reverted:type_name -> common.RevertedCondition - 143, // 213: common.QueryFilter.audit:type_name -> common.AuditCondition - 154, // 214: common.ReferenceCondition.cond:type_name -> common.StringCondition - 14, // 215: common.AuditCondition.field:type_name -> common.AuditField - 154, // 216: common.AuditCondition.string_cond:type_name -> common.StringCondition - 156, // 217: common.AuditCondition.uint_cond:type_name -> common.UintCondition - 154, // 218: common.LedgerCondition.cond:type_name -> common.StringCondition - 156, // 219: common.LogIdCondition.cond:type_name -> common.UintCondition - 3, // 220: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex - 156, // 221: common.BuiltinUintCondition.cond:type_name -> common.UintCondition - 5, // 222: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex - 156, // 223: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition - 140, // 224: common.AndFilter.filters:type_name -> common.QueryFilter - 140, // 225: common.OrFilter.filters:type_name -> common.QueryFilter - 140, // 226: common.NotFilter.filter:type_name -> common.QueryFilter - 152, // 227: common.FieldCondition.field:type_name -> common.FieldRef - 154, // 228: common.FieldCondition.string_cond:type_name -> common.StringCondition - 155, // 229: common.FieldCondition.int_cond:type_name -> common.IntCondition - 156, // 230: common.FieldCondition.uint_cond:type_name -> common.UintCondition - 157, // 231: common.FieldCondition.bool_cond:type_name -> common.BoolCondition - 158, // 232: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition - 15, // 233: common.AddressMatch.role:type_name -> common.AddressRole - 140, // 234: common.PreparedQuery.filter:type_name -> common.QueryFilter - 16, // 235: common.PreparedQuery.target:type_name -> common.QueryTarget - 23, // 236: common.AggregatedVolume.input:type_name -> common.Uint256 - 23, // 237: common.AggregatedVolume.output:type_name -> common.Uint256 - 161, // 238: common.AggregateResult.volumes:type_name -> common.AggregatedVolume - 163, // 239: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult - 161, // 240: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume - 31, // 241: common.PreparedQueryCursor.account_data:type_name -> common.Account - 25, // 242: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction - 167, // 243: common.CallerSnapshot.identity:type_name -> common.CallerIdentity - 169, // 244: common.BackupStorage.s3:type_name -> common.S3StorageConfig - 170, // 245: common.BackupStorage.azure:type_name -> common.AzureStorageConfig - 172, // 246: common.ListOptions.read:type_name -> common.ReadOptions - 140, // 247: common.ListOptions.filter:type_name -> common.QueryFilter - 20, // 248: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue - 20, // 249: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue - 27, // 250: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes - 29, // 251: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets - 20, // 252: common.Account.MetadataEntry.value:type_name -> common.MetadataValue - 28, // 253: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance - 34, // 254: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema - 34, // 255: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema - 34, // 256: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema - 20, // 257: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue - 136, // 258: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType - 21, // 259: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap - 20, // 260: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue - 136, // 261: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType - 20, // 262: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue - 20, // 263: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue - 20, // 264: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue - 132, // 265: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType - 266, // [266:266] is the sub-list for method output_type - 266, // [266:266] is the sub-list for method input_type - 266, // [266:266] is the sub-list for extension type_name - 266, // [266:266] is the sub-list for extension extendee - 0, // [0:266] is the sub-list for field type_name + 18, // 83: common.TemplateUsage.last_used:type_name -> common.Timestamp + 73, // 84: common.SinkConfig.nats:type_name -> common.NatsSinkConfig + 74, // 85: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig + 75, // 86: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig + 76, // 87: common.SinkConfig.http:type_name -> common.HttpSinkConfig + 77, // 88: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig + 7, // 89: common.SinkConfig.event_types:type_name -> common.EventType + 72, // 90: common.SinkStatus.error:type_name -> common.SinkError + 18, // 91: common.SinkError.occurred_at:type_name -> common.Timestamp + 78, // 92: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M + 18, // 93: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp + 35, // 94: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema + 9, // 95: common.CreatedLedgerLog.mode:type_name -> common.LedgerMode + 99, // 96: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig + 186, // 97: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry + 12, // 98: common.CreatedLedgerLog.default_enforcement_mode:type_name -> common.ChartEnforcementMode + 18, // 99: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp + 82, // 100: common.ApplyLedgerLog.log:type_name -> common.LedgerLog + 84, // 101: common.LedgerLog.data:type_name -> common.LedgerLogPayload + 18, // 102: common.LedgerLog.date:type_name -> common.Timestamp + 83, // 103: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume + 88, // 104: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction + 89, // 105: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction + 90, // 106: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata + 91, // 107: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata + 92, // 108: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog + 93, // 109: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog + 87, // 110: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog + 85, // 111: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog + 86, // 112: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog + 138, // 113: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog + 139, // 114: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog + 140, // 115: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog + 38, // 116: common.CreatedIndexLog.id:type_name -> common.IndexID + 38, // 117: common.DroppedIndexLog.id:type_name -> common.IndexID + 25, // 118: common.CreatedTransaction.transaction:type_name -> common.Transaction + 187, // 119: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry + 30, // 120: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 25, // 121: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction + 30, // 122: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 33, // 123: common.SavedMetadata.target:type_name -> common.Target + 188, // 124: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry + 33, // 125: common.DeletedMetadata.target:type_name -> common.Target + 0, // 126: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 1, // 127: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType + 0, // 128: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 38, // 129: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID + 18, // 130: common.Chapter.start:type_name -> common.Timestamp + 18, // 131: common.Chapter.end:type_name -> common.Timestamp + 8, // 132: common.Chapter.status:type_name -> common.ChapterStatus + 94, // 133: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter + 94, // 134: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter + 94, // 135: common.SealedChapterLog.chapter:type_name -> common.Chapter + 94, // 136: common.ArchivedChapterLog.chapter:type_name -> common.Chapter + 94, // 137: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter + 119, // 138: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig + 121, // 139: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig + 100, // 140: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule + 101, // 141: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule + 102, // 142: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule + 103, // 143: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule + 104, // 144: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule + 105, // 145: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule + 106, // 146: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction + 107, // 147: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction + 108, // 148: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction + 109, // 149: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction + 110, // 150: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction + 111, // 151: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 152: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 153: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 114, // 154: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction + 115, // 155: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction + 116, // 156: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction + 118, // 157: common.CreatedTransactionAction.drop:type_name -> common.DropAction + 111, // 158: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 159: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 160: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 118, // 161: common.RevertedTransactionAction.drop:type_name -> common.DropAction + 111, // 162: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 163: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 164: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction + 118, // 165: common.SavedMetadataAction.drop:type_name -> common.DropAction + 111, // 166: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 118, // 167: common.DeletedMetadataAction.drop:type_name -> common.DropAction + 111, // 168: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction + 118, // 169: common.AnyVariantAction.drop:type_name -> common.DropAction + 117, // 170: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement + 120, // 171: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials + 122, // 172: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth + 18, // 173: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp + 10, // 174: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState + 123, // 175: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError + 18, // 176: common.LedgerInfo.created_at:type_name -> common.Timestamp + 18, // 177: common.LedgerInfo.deleted_at:type_name -> common.Timestamp + 35, // 178: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema + 9, // 179: common.LedgerInfo.mode:type_name -> common.LedgerMode + 99, // 180: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig + 124, // 181: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress + 189, // 182: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry + 12, // 183: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode + 190, // 184: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry + 33, // 185: common.SaveMetadataCommand.target:type_name -> common.Target + 191, // 186: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry + 33, // 187: common.DeleteMetadataCommand.target:type_name -> common.Target + 192, // 188: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry + 18, // 189: common.TransactionState.timestamp:type_name -> common.Timestamp + 24, // 190: common.TransactionState.postings:type_name -> common.Posting + 18, // 191: common.TransactionState.reverted_at:type_name -> common.Timestamp + 130, // 192: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure + 11, // 193: common.IdempotencyFailure.reason:type_name -> common.ErrorReason + 193, // 194: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry + 134, // 195: common.SegmentType.uuid:type_name -> common.UUIDConstraint + 135, // 196: common.SegmentType.uint64:type_name -> common.Uint64Constraint + 136, // 197: common.SegmentType.bytes:type_name -> common.BytesConstraint + 13, // 198: common.AccountType.persistence:type_name -> common.AccountTypePersistence + 194, // 199: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry + 137, // 200: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType + 12, // 201: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode + 154, // 202: common.QueryFilter.field:type_name -> common.FieldCondition + 160, // 203: common.QueryFilter.address:type_name -> common.AddressMatch + 150, // 204: common.QueryFilter.and:type_name -> common.AndFilter + 151, // 205: common.QueryFilter.or:type_name -> common.OrFilter + 152, // 206: common.QueryFilter.not:type_name -> common.NotFilter + 142, // 207: common.QueryFilter.reference:type_name -> common.ReferenceCondition + 147, // 208: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition + 145, // 209: common.QueryFilter.ledger:type_name -> common.LedgerCondition + 146, // 210: common.QueryFilter.log_id:type_name -> common.LogIdCondition + 148, // 211: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition + 149, // 212: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition + 143, // 213: common.QueryFilter.reverted:type_name -> common.RevertedCondition + 144, // 214: common.QueryFilter.audit:type_name -> common.AuditCondition + 155, // 215: common.ReferenceCondition.cond:type_name -> common.StringCondition + 14, // 216: common.AuditCondition.field:type_name -> common.AuditField + 155, // 217: common.AuditCondition.string_cond:type_name -> common.StringCondition + 157, // 218: common.AuditCondition.uint_cond:type_name -> common.UintCondition + 155, // 219: common.LedgerCondition.cond:type_name -> common.StringCondition + 157, // 220: common.LogIdCondition.cond:type_name -> common.UintCondition + 3, // 221: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex + 157, // 222: common.BuiltinUintCondition.cond:type_name -> common.UintCondition + 5, // 223: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex + 157, // 224: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition + 141, // 225: common.AndFilter.filters:type_name -> common.QueryFilter + 141, // 226: common.OrFilter.filters:type_name -> common.QueryFilter + 141, // 227: common.NotFilter.filter:type_name -> common.QueryFilter + 153, // 228: common.FieldCondition.field:type_name -> common.FieldRef + 155, // 229: common.FieldCondition.string_cond:type_name -> common.StringCondition + 156, // 230: common.FieldCondition.int_cond:type_name -> common.IntCondition + 157, // 231: common.FieldCondition.uint_cond:type_name -> common.UintCondition + 158, // 232: common.FieldCondition.bool_cond:type_name -> common.BoolCondition + 159, // 233: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition + 15, // 234: common.AddressMatch.role:type_name -> common.AddressRole + 141, // 235: common.PreparedQuery.filter:type_name -> common.QueryFilter + 16, // 236: common.PreparedQuery.target:type_name -> common.QueryTarget + 23, // 237: common.AggregatedVolume.input:type_name -> common.Uint256 + 23, // 238: common.AggregatedVolume.output:type_name -> common.Uint256 + 162, // 239: common.AggregateResult.volumes:type_name -> common.AggregatedVolume + 164, // 240: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult + 162, // 241: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume + 31, // 242: common.PreparedQueryCursor.account_data:type_name -> common.Account + 25, // 243: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction + 168, // 244: common.CallerSnapshot.identity:type_name -> common.CallerIdentity + 170, // 245: common.BackupStorage.s3:type_name -> common.S3StorageConfig + 171, // 246: common.BackupStorage.azure:type_name -> common.AzureStorageConfig + 173, // 247: common.ListOptions.read:type_name -> common.ReadOptions + 141, // 248: common.ListOptions.filter:type_name -> common.QueryFilter + 20, // 249: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue + 20, // 250: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue + 27, // 251: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes + 29, // 252: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets + 20, // 253: common.Account.MetadataEntry.value:type_name -> common.MetadataValue + 28, // 254: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance + 34, // 255: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema + 34, // 256: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema + 34, // 257: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema + 20, // 258: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue + 137, // 259: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType + 21, // 260: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap + 20, // 261: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue + 137, // 262: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType + 20, // 263: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue + 20, // 264: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue + 20, // 265: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue + 133, // 266: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType + 267, // [267:267] is the sub-list for method output_type + 267, // [267:267] is the sub-list for method input_type + 267, // [267:267] is the sub-list for extension type_name + 267, // [267:267] is the sub-list for extension extendee + 0, // [0:267] is the sub-list for field type_name } func init() { file_common_proto_init() } @@ -13961,18 +14021,18 @@ func file_common_proto_init() { (*LogPayload_SavedLedgerMetadata)(nil), (*LogPayload_DeletedLedgerMetadata)(nil), } - file_common_proto_msgTypes[51].OneofWrappers = []any{ + file_common_proto_msgTypes[52].OneofWrappers = []any{ (*SinkConfig_Nats)(nil), (*SinkConfig_Clickhouse)(nil), (*SinkConfig_Kafka)(nil), (*SinkConfig_Http)(nil), (*SinkConfig_Databricks)(nil), } - file_common_proto_msgTypes[58].OneofWrappers = []any{ + file_common_proto_msgTypes[59].OneofWrappers = []any{ (*DatabricksSinkConfig_Token)(nil), (*DatabricksSinkConfig_OauthM2M)(nil), } - file_common_proto_msgTypes[65].OneofWrappers = []any{ + file_common_proto_msgTypes[66].OneofWrappers = []any{ (*LedgerLogPayload_CreatedTransaction)(nil), (*LedgerLogPayload_RevertedTransaction)(nil), (*LedgerLogPayload_SavedMetadata)(nil), @@ -13986,18 +14046,18 @@ func file_common_proto_init() { (*LedgerLogPayload_RemovedAccountType)(nil), (*LedgerLogPayload_UpdatedDefaultEnforcementMode)(nil), } - file_common_proto_msgTypes[80].OneofWrappers = []any{ + file_common_proto_msgTypes[81].OneofWrappers = []any{ (*MirrorSourceConfig_Http)(nil), (*MirrorSourceConfig_Postgres)(nil), } - file_common_proto_msgTypes[81].OneofWrappers = []any{ + file_common_proto_msgTypes[82].OneofWrappers = []any{ (*MirrorRewriteRule_CreatedTransaction)(nil), (*MirrorRewriteRule_RevertedTransaction)(nil), (*MirrorRewriteRule_SavedMetadata)(nil), (*MirrorRewriteRule_DeletedMetadata)(nil), (*MirrorRewriteRule_AnyVariant)(nil), } - file_common_proto_msgTypes[87].OneofWrappers = []any{ + file_common_proto_msgTypes[88].OneofWrappers = []any{ (*CreatedTransactionAction_RewriteAddress)(nil), (*CreatedTransactionAction_SetMetadata)(nil), (*CreatedTransactionAction_DeleteMetadata)(nil), @@ -14006,41 +14066,41 @@ func file_common_proto_init() { (*CreatedTransactionAction_SetAccountMetadataFromAddress)(nil), (*CreatedTransactionAction_Drop)(nil), } - file_common_proto_msgTypes[88].OneofWrappers = []any{ + file_common_proto_msgTypes[89].OneofWrappers = []any{ (*RevertedTransactionAction_RewriteAddress)(nil), (*RevertedTransactionAction_SetMetadata)(nil), (*RevertedTransactionAction_DeleteMetadata)(nil), (*RevertedTransactionAction_Drop)(nil), } - file_common_proto_msgTypes[89].OneofWrappers = []any{ + file_common_proto_msgTypes[90].OneofWrappers = []any{ (*SavedMetadataAction_RewriteAddress)(nil), (*SavedMetadataAction_SetMetadata)(nil), (*SavedMetadataAction_DeleteMetadata)(nil), (*SavedMetadataAction_Drop)(nil), } - file_common_proto_msgTypes[90].OneofWrappers = []any{ + file_common_proto_msgTypes[91].OneofWrappers = []any{ (*DeletedMetadataAction_RewriteAddress)(nil), (*DeletedMetadataAction_Drop)(nil), } - file_common_proto_msgTypes[91].OneofWrappers = []any{ + file_common_proto_msgTypes[92].OneofWrappers = []any{ (*AnyVariantAction_RewriteAddress)(nil), (*AnyVariantAction_Drop)(nil), } - file_common_proto_msgTypes[93].OneofWrappers = []any{ + file_common_proto_msgTypes[94].OneofWrappers = []any{ (*SetMetadataAction_Value)(nil), (*SetMetadataAction_ValueExpr)(nil), } - file_common_proto_msgTypes[95].OneofWrappers = []any{ + file_common_proto_msgTypes[96].OneofWrappers = []any{ (*SetAccountMetadataAction_Value)(nil), (*SetAccountMetadataAction_ValueExpr)(nil), } - file_common_proto_msgTypes[114].OneofWrappers = []any{ + file_common_proto_msgTypes[115].OneofWrappers = []any{ (*SegmentType_Regex)(nil), (*SegmentType_Uuid)(nil), (*SegmentType_Uint64)(nil), (*SegmentType_Bytes)(nil), } - file_common_proto_msgTypes[122].OneofWrappers = []any{ + file_common_proto_msgTypes[123].OneofWrappers = []any{ (*QueryFilter_Field)(nil), (*QueryFilter_Address)(nil), (*QueryFilter_And)(nil), @@ -14055,39 +14115,39 @@ func file_common_proto_init() { (*QueryFilter_Reverted)(nil), (*QueryFilter_Audit)(nil), } - file_common_proto_msgTypes[125].OneofWrappers = []any{ + file_common_proto_msgTypes[126].OneofWrappers = []any{ (*AuditCondition_StringCond)(nil), (*AuditCondition_UintCond)(nil), } - file_common_proto_msgTypes[135].OneofWrappers = []any{ + file_common_proto_msgTypes[136].OneofWrappers = []any{ (*FieldCondition_StringCond)(nil), (*FieldCondition_IntCond)(nil), (*FieldCondition_UintCond)(nil), (*FieldCondition_BoolCond)(nil), (*FieldCondition_ExistsCond)(nil), } - file_common_proto_msgTypes[136].OneofWrappers = []any{ + file_common_proto_msgTypes[137].OneofWrappers = []any{ (*StringCondition_Hardcoded)(nil), (*StringCondition_Param)(nil), } - file_common_proto_msgTypes[137].OneofWrappers = []any{} file_common_proto_msgTypes[138].OneofWrappers = []any{} - file_common_proto_msgTypes[139].OneofWrappers = []any{ + file_common_proto_msgTypes[139].OneofWrappers = []any{} + file_common_proto_msgTypes[140].OneofWrappers = []any{ (*BoolCondition_Hardcoded)(nil), (*BoolCondition_Param)(nil), } - file_common_proto_msgTypes[141].OneofWrappers = []any{ + file_common_proto_msgTypes[142].OneofWrappers = []any{ (*AddressMatch_HardcodedPrefix)(nil), (*AddressMatch_HardcodedExact)(nil), (*AddressMatch_ParamPrefix)(nil), (*AddressMatch_ParamExact)(nil), } - file_common_proto_msgTypes[149].OneofWrappers = []any{ + file_common_proto_msgTypes[150].OneofWrappers = []any{ (*CallerIdentity_Issuer)(nil), (*CallerIdentity_KeyId)(nil), (*CallerIdentity_SystemComponent)(nil), } - file_common_proto_msgTypes[153].OneofWrappers = []any{ + file_common_proto_msgTypes[154].OneofWrappers = []any{ (*BackupStorage_S3)(nil), (*BackupStorage_Azure)(nil), } @@ -14097,7 +14157,7 @@ func file_common_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), NumEnums: 18, - NumMessages: 176, + NumMessages: 177, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/commonpb/common_dethash.pb.go b/internal/proto/commonpb/common_dethash.pb.go index ff002a3e0a..d832cc3ba2 100644 --- a/internal/proto/commonpb/common_dethash.pb.go +++ b/internal/proto/commonpb/common_dethash.pb.go @@ -1347,6 +1347,17 @@ func (m *DeletedNumscriptLog) MarshalDeterministicVT(dAtA []byte) []byte { return append(dAtA, b...) } +func (m *TemplateUsage) MarshalDeterministicVT(dAtA []byte) []byte { + if m == nil { + return dAtA + } + b, err := m.MarshalVT() + if err != nil { + panic("MarshalDeterministicVT: " + err.Error()) + } + return append(dAtA, b...) +} + func (m *SetQueryCheckpointScheduleLog) MarshalDeterministicVT(dAtA []byte) []byte { if m == nil { return dAtA diff --git a/internal/proto/commonpb/common_reader.pb.go b/internal/proto/commonpb/common_reader.pb.go index 2044531d03..f980537270 100644 --- a/internal/proto/commonpb/common_reader.pb.go +++ b/internal/proto/commonpb/common_reader.pb.go @@ -3965,6 +3965,82 @@ func NewDeletedNumscriptLogListReader(s []*DeletedNumscriptLog) DeletedNumscript return deletedNumscriptLogListReadonly(s) } +// TemplateUsageReader provides read-only access to TemplateUsage. +// Call Mutate() to obtain a mutable clone. +type TemplateUsageReader interface { + GetCount() uint64 + GetLastUsed() TimestampReader + Mutate() *TemplateUsage +} + +type templateUsageReadonly TemplateUsage + +func (r *templateUsageReadonly) GetCount() uint64 { + return (*TemplateUsage)(r).GetCount() +} + +func (r *templateUsageReadonly) GetLastUsed() TimestampReader { + v := (*TemplateUsage)(r).GetLastUsed() + if v == nil { + return nil + } + return v.AsReader() +} + +func (r *templateUsageReadonly) Mutate() *TemplateUsage { + return (*TemplateUsage)(r).CloneVT() +} + +// AsReader returns a read-only view of this TemplateUsage. +func (m *TemplateUsage) AsReader() TemplateUsageReader { + if m == nil { + return nil + } + return (*templateUsageReadonly)(m) +} + +// Mutate returns a mutable deep clone of this TemplateUsage. +func (m *TemplateUsage) Mutate() *TemplateUsage { + return m.CloneVT() +} + +// TemplateUsageListReader provides read-only iteration over []*TemplateUsage. +type TemplateUsageListReader interface { + Len() int + Get(i int) TemplateUsageReader + Range(yield func(int, TemplateUsageReader) bool) +} + +type templateUsageListReadonly []*TemplateUsage + +func (l templateUsageListReadonly) Len() int { return len(l) } + +func (l templateUsageListReadonly) Get(i int) TemplateUsageReader { + v := l[i] + if v == nil { + return nil + } + return v.AsReader() +} + +func (l templateUsageListReadonly) Range(yield func(int, TemplateUsageReader) bool) { + for i, v := range l { + var r TemplateUsageReader + if v != nil { + r = v.AsReader() + } + if !yield(i, r) { + return + } + } +} + +// NewTemplateUsageListReader wraps s for read-only iteration. The returned +// view aliases the underlying slice; do not mutate s afterwards. +func NewTemplateUsageListReader(s []*TemplateUsage) TemplateUsageListReader { + return templateUsageListReadonly(s) +} + // SetQueryCheckpointScheduleLogReader provides read-only access to SetQueryCheckpointScheduleLog. // Call Mutate() to obtain a mutable clone. type SetQueryCheckpointScheduleLogReader interface { diff --git a/internal/proto/commonpb/common_vtproto.pb.go b/internal/proto/commonpb/common_vtproto.pb.go index c2edb860b4..73343c14bf 100644 --- a/internal/proto/commonpb/common_vtproto.pb.go +++ b/internal/proto/commonpb/common_vtproto.pb.go @@ -1382,6 +1382,24 @@ func (m *DeletedNumscriptLog) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *TemplateUsage) CloneVT() *TemplateUsage { + if m == nil { + return (*TemplateUsage)(nil) + } + r := new(TemplateUsage) + r.Count = m.Count + r.LastUsed = m.LastUsed.CloneVT() + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *TemplateUsage) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *SetQueryCheckpointScheduleLog) CloneVT() *SetQueryCheckpointScheduleLog { if m == nil { return (*SetQueryCheckpointScheduleLog)(nil) @@ -6763,6 +6781,28 @@ func (this *DeletedNumscriptLog) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *TemplateUsage) EqualVT(that *TemplateUsage) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Count != that.Count { + return false + } + if !this.LastUsed.EqualVT(that.LastUsed) { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *TemplateUsage) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*TemplateUsage) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *SetQueryCheckpointScheduleLog) EqualVT(that *SetQueryCheckpointScheduleLog) bool { if this == that { return true @@ -15329,6 +15369,55 @@ func (m *DeletedNumscriptLog) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *TemplateUsage) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TemplateUsage) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *TemplateUsage) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.LastUsed != nil { + size, err := m.LastUsed.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if m.Count != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Count)) + i-- + dAtA[i] = 0x9 + } + return len(dAtA) - i, nil +} + func (m *SetQueryCheckpointScheduleLog) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -24261,6 +24350,23 @@ func (m *DeletedNumscriptLog) SizeVT() (n int) { return n } +func (m *TemplateUsage) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Count != 0 { + n += 9 + } + if m.LastUsed != nil { + l = m.LastUsed.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *SetQueryCheckpointScheduleLog) SizeVT() (n int) { if m == nil { return 0 @@ -35803,6 +35909,103 @@ func (m *DeletedNumscriptLog) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *TemplateUsage) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TemplateUsage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TemplateUsage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Count = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastUsed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LastUsed == nil { + m.LastUsed = &Timestamp{} + } + if err := m.LastUsed.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *SetQueryCheckpointScheduleLog) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/internal/proto/raftcmdpb/raft_cmd.pb.go b/internal/proto/raftcmdpb/raft_cmd.pb.go index 04d3fcc535..418976a4b4 100644 --- a/internal/proto/raftcmdpb/raft_cmd.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd.pb.go @@ -4882,19 +4882,15 @@ func (*CreatedLogOrReference_CreatedLog) isCreatedLogOrReference_Type() {} func (*CreatedLogOrReference_ReferenceSequence) isCreatedLogOrReference_Type() {} type LedgerBoundaries struct { - state protoimpl.MessageState `protogen:"open.v1"` - NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` - NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` - VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` - MetadataCount uint64 `protobuf:"fixed64,4,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` - ReferenceCount uint64 `protobuf:"fixed64,5,opt,name=reference_count,json=referenceCount,proto3" json:"reference_count,omitempty"` - PostingCount uint64 `protobuf:"fixed64,6,opt,name=posting_count,json=postingCount,proto3" json:"posting_count,omitempty"` - EphemeralEvictedCount uint64 `protobuf:"fixed64,7,opt,name=ephemeral_evicted_count,json=ephemeralEvictedCount,proto3" json:"ephemeral_evicted_count,omitempty"` - TransientUsedCount uint64 `protobuf:"fixed64,8,opt,name=transient_used_count,json=transientUsedCount,proto3" json:"transient_used_count,omitempty"` - RevertCount uint64 `protobuf:"fixed64,9,opt,name=revert_count,json=revertCount,proto3" json:"revert_count,omitempty"` - NumscriptExecutionCount uint64 `protobuf:"fixed64,10,opt,name=numscript_execution_count,json=numscriptExecutionCount,proto3" json:"numscript_execution_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` + NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` + VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` + MetadataCount uint64 `protobuf:"fixed64,4,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` + EphemeralEvictedCount uint64 `protobuf:"fixed64,7,opt,name=ephemeral_evicted_count,json=ephemeralEvictedCount,proto3" json:"ephemeral_evicted_count,omitempty"` + TransientUsedCount uint64 `protobuf:"fixed64,8,opt,name=transient_used_count,json=transientUsedCount,proto3" json:"transient_used_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LedgerBoundaries) Reset() { @@ -4955,20 +4951,6 @@ func (x *LedgerBoundaries) GetMetadataCount() uint64 { return 0 } -func (x *LedgerBoundaries) GetReferenceCount() uint64 { - if x != nil { - return x.ReferenceCount - } - return 0 -} - -func (x *LedgerBoundaries) GetPostingCount() uint64 { - if x != nil { - return x.PostingCount - } - return 0 -} - func (x *LedgerBoundaries) GetEphemeralEvictedCount() uint64 { if x != nil { return x.EphemeralEvictedCount @@ -4983,20 +4965,6 @@ func (x *LedgerBoundaries) GetTransientUsedCount() uint64 { return 0 } -func (x *LedgerBoundaries) GetRevertCount() uint64 { - if x != nil { - return x.RevertCount - } - return 0 -} - -func (x *LedgerBoundaries) GetNumscriptExecutionCount() uint64 { - if x != nil { - return x.NumscriptExecutionCount - } - return 0 -} - type VolumePair struct { state protoimpl.MessageState `protogen:"open.v1"` Input *commonpb.Uint256 `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` @@ -6422,19 +6390,14 @@ const file_raft_cmd_proto_rawDesc = "" + "\vcreated_log\x18\x01 \x01(\v2\v.common.LogH\x00R\n" + "createdLog\x12/\n" + "\x12reference_sequence\x18\x02 \x01(\x06H\x00R\x11referenceSequenceB\x06\n" + - "\x04type\"\xc3\x03\n" + + "\x04type\"\x96\x02\n" + "\x10LedgerBoundaries\x12.\n" + "\x13next_transaction_id\x18\x01 \x01(\x06R\x11nextTransactionId\x12\x1e\n" + "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\x12!\n" + "\fvolume_count\x18\x03 \x01(\x06R\vvolumeCount\x12%\n" + - "\x0emetadata_count\x18\x04 \x01(\x06R\rmetadataCount\x12'\n" + - "\x0freference_count\x18\x05 \x01(\x06R\x0ereferenceCount\x12#\n" + - "\rposting_count\x18\x06 \x01(\x06R\fpostingCount\x126\n" + + "\x0emetadata_count\x18\x04 \x01(\x06R\rmetadataCount\x126\n" + "\x17ephemeral_evicted_count\x18\a \x01(\x06R\x15ephemeralEvictedCount\x120\n" + - "\x14transient_used_count\x18\b \x01(\x06R\x12transientUsedCount\x12!\n" + - "\frevert_count\x18\t \x01(\x06R\vrevertCount\x12:\n" + - "\x19numscript_execution_count\x18\n" + - " \x01(\x06R\x17numscriptExecutionCount\"\\\n" + + "\x14transient_used_count\x18\b \x01(\x06R\x12transientUsedCount\"\\\n" + "\n" + "VolumePair\x12%\n" + "\x05input\x18\x01 \x01(\v2\x0f.common.Uint256R\x05input\x12'\n" + diff --git a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go index ffdf7bebfb..eb8e195094 100644 --- a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go @@ -5251,12 +5251,8 @@ type LedgerBoundariesReader interface { GetNextLogId() uint64 GetVolumeCount() uint64 GetMetadataCount() uint64 - GetReferenceCount() uint64 - GetPostingCount() uint64 GetEphemeralEvictedCount() uint64 GetTransientUsedCount() uint64 - GetRevertCount() uint64 - GetNumscriptExecutionCount() uint64 Mutate() *LedgerBoundaries } @@ -5278,14 +5274,6 @@ func (r *ledgerBoundariesReadonly) GetMetadataCount() uint64 { return (*LedgerBoundaries)(r).GetMetadataCount() } -func (r *ledgerBoundariesReadonly) GetReferenceCount() uint64 { - return (*LedgerBoundaries)(r).GetReferenceCount() -} - -func (r *ledgerBoundariesReadonly) GetPostingCount() uint64 { - return (*LedgerBoundaries)(r).GetPostingCount() -} - func (r *ledgerBoundariesReadonly) GetEphemeralEvictedCount() uint64 { return (*LedgerBoundaries)(r).GetEphemeralEvictedCount() } @@ -5294,14 +5282,6 @@ func (r *ledgerBoundariesReadonly) GetTransientUsedCount() uint64 { return (*LedgerBoundaries)(r).GetTransientUsedCount() } -func (r *ledgerBoundariesReadonly) GetRevertCount() uint64 { - return (*LedgerBoundaries)(r).GetRevertCount() -} - -func (r *ledgerBoundariesReadonly) GetNumscriptExecutionCount() uint64 { - return (*LedgerBoundaries)(r).GetNumscriptExecutionCount() -} - func (r *ledgerBoundariesReadonly) Mutate() *LedgerBoundaries { return (*LedgerBoundaries)(r).CloneVT() } diff --git a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go index 9fdf3df744..302d41e5de 100644 --- a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go @@ -1962,12 +1962,8 @@ func (m *LedgerBoundaries) CloneVT() *LedgerBoundaries { r.NextLogId = m.NextLogId r.VolumeCount = m.VolumeCount r.MetadataCount = m.MetadataCount - r.ReferenceCount = m.ReferenceCount - r.PostingCount = m.PostingCount r.EphemeralEvictedCount = m.EphemeralEvictedCount r.TransientUsedCount = m.TransientUsedCount - r.RevertCount = m.RevertCount - r.NumscriptExecutionCount = m.NumscriptExecutionCount if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -5815,24 +5811,12 @@ func (this *LedgerBoundaries) EqualVT(that *LedgerBoundaries) bool { if this.MetadataCount != that.MetadataCount { return false } - if this.ReferenceCount != that.ReferenceCount { - return false - } - if this.PostingCount != that.PostingCount { - return false - } if this.EphemeralEvictedCount != that.EphemeralEvictedCount { return false } if this.TransientUsedCount != that.TransientUsedCount { return false } - if this.RevertCount != that.RevertCount { - return false - } - if this.NumscriptExecutionCount != that.NumscriptExecutionCount { - return false - } return string(this.unknownFields) == string(that.unknownFields) } @@ -11082,18 +11066,6 @@ func (m *LedgerBoundaries) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.NumscriptExecutionCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.NumscriptExecutionCount)) - i-- - dAtA[i] = 0x51 - } - if m.RevertCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.RevertCount)) - i-- - dAtA[i] = 0x49 - } if m.TransientUsedCount != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TransientUsedCount)) @@ -11106,18 +11078,6 @@ func (m *LedgerBoundaries) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x39 } - if m.PostingCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.PostingCount)) - i-- - dAtA[i] = 0x31 - } - if m.ReferenceCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.ReferenceCount)) - i-- - dAtA[i] = 0x29 - } if m.MetadataCount != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.MetadataCount)) @@ -14401,24 +14361,12 @@ func (m *LedgerBoundaries) SizeVT() (n int) { if m.MetadataCount != 0 { n += 9 } - if m.ReferenceCount != 0 { - n += 9 - } - if m.PostingCount != 0 { - n += 9 - } if m.EphemeralEvictedCount != 0 { n += 9 } if m.TransientUsedCount != 0 { n += 9 } - if m.RevertCount != 0 { - n += 9 - } - if m.NumscriptExecutionCount != 0 { - n += 9 - } n += len(m.unknownFields) return n } @@ -25606,26 +25554,6 @@ func (m *LedgerBoundaries) UnmarshalVT(dAtA []byte) error { } m.MetadataCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 5: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field ReferenceCount", wireType) - } - m.ReferenceCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.ReferenceCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - case 6: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field PostingCount", wireType) - } - m.PostingCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.PostingCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 case 7: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field EphemeralEvictedCount", wireType) @@ -25646,26 +25574,6 @@ func (m *LedgerBoundaries) UnmarshalVT(dAtA []byte) error { } m.TransientUsedCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 9: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field RevertCount", wireType) - } - m.RevertCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.RevertCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - case 10: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field NumscriptExecutionCount", wireType) - } - m.NumscriptExecutionCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.NumscriptExecutionCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/internal/proto/servicepb/bucket.pb.go b/internal/proto/servicepb/bucket.pb.go index 92912ba749..f59cad2e76 100644 --- a/internal/proto/servicepb/bucket.pb.go +++ b/internal/proto/servicepb/bucket.pb.go @@ -310,7 +310,7 @@ func (x ListIndexesRequest_Scope) Number() protoreflect.EnumNumber { // Deprecated: Use ListIndexesRequest_Scope.Descriptor instead. func (ListIndexesRequest_Scope) EnumDescriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{115, 0} + return file_bucket_proto_rawDescGZIP(), []int{116, 0} } type GetAccountRequest struct { @@ -3022,6 +3022,62 @@ func (x *ListNumscriptsRequest) GetOptions() *commonpb.ListOptions { return nil } +// GetTemplateUsageRequest retrieves the invocation counter + last-used +// timestamp for a Numscript template. The counter is materialised +// asynchronously by the usagebuilder from the audit chain — expect up to +// one tick interval of lag behind the live FSM. +type GetTemplateUsageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ledger string `protobuf:"bytes,1,opt,name=ledger,proto3" json:"ledger,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTemplateUsageRequest) Reset() { + *x = GetTemplateUsageRequest{} + mi := &file_bucket_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTemplateUsageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTemplateUsageRequest) ProtoMessage() {} + +func (x *GetTemplateUsageRequest) ProtoReflect() protoreflect.Message { + mi := &file_bucket_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTemplateUsageRequest.ProtoReflect.Descriptor instead. +func (*GetTemplateUsageRequest) Descriptor() ([]byte, []int) { + return file_bucket_proto_rawDescGZIP(), []int{41} +} + +func (x *GetTemplateUsageRequest) GetLedger() string { + if x != nil { + return x.Ledger + } + return "" +} + +func (x *GetTemplateUsageRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + // ScriptReference references a numscript from the library by name and optional version. type ScriptReference struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3034,7 +3090,7 @@ type ScriptReference struct { func (x *ScriptReference) Reset() { *x = ScriptReference{} - mi := &file_bucket_proto_msgTypes[41] + mi := &file_bucket_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3046,7 +3102,7 @@ func (x *ScriptReference) String() string { func (*ScriptReference) ProtoMessage() {} func (x *ScriptReference) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[41] + mi := &file_bucket_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3059,7 +3115,7 @@ func (x *ScriptReference) ProtoReflect() protoreflect.Message { // Deprecated: Use ScriptReference.ProtoReflect.Descriptor instead. func (*ScriptReference) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{41} + return file_bucket_proto_rawDescGZIP(), []int{42} } func (x *ScriptReference) GetName() string { @@ -3093,7 +3149,7 @@ type SetQueryCheckpointScheduleRequest struct { func (x *SetQueryCheckpointScheduleRequest) Reset() { *x = SetQueryCheckpointScheduleRequest{} - mi := &file_bucket_proto_msgTypes[42] + mi := &file_bucket_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3105,7 +3161,7 @@ func (x *SetQueryCheckpointScheduleRequest) String() string { func (*SetQueryCheckpointScheduleRequest) ProtoMessage() {} func (x *SetQueryCheckpointScheduleRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[42] + mi := &file_bucket_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3118,7 +3174,7 @@ func (x *SetQueryCheckpointScheduleRequest) ProtoReflect() protoreflect.Message // Deprecated: Use SetQueryCheckpointScheduleRequest.ProtoReflect.Descriptor instead. func (*SetQueryCheckpointScheduleRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{42} + return file_bucket_proto_rawDescGZIP(), []int{43} } func (x *SetQueryCheckpointScheduleRequest) GetCron() string { @@ -3137,7 +3193,7 @@ type DeleteQueryCheckpointScheduleRequest struct { func (x *DeleteQueryCheckpointScheduleRequest) Reset() { *x = DeleteQueryCheckpointScheduleRequest{} - mi := &file_bucket_proto_msgTypes[43] + mi := &file_bucket_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3149,7 +3205,7 @@ func (x *DeleteQueryCheckpointScheduleRequest) String() string { func (*DeleteQueryCheckpointScheduleRequest) ProtoMessage() {} func (x *DeleteQueryCheckpointScheduleRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[43] + mi := &file_bucket_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3162,7 +3218,7 @@ func (x *DeleteQueryCheckpointScheduleRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use DeleteQueryCheckpointScheduleRequest.ProtoReflect.Descriptor instead. func (*DeleteQueryCheckpointScheduleRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{43} + return file_bucket_proto_rawDescGZIP(), []int{44} } type GetChapterScheduleRequest struct { @@ -3173,7 +3229,7 @@ type GetChapterScheduleRequest struct { func (x *GetChapterScheduleRequest) Reset() { *x = GetChapterScheduleRequest{} - mi := &file_bucket_proto_msgTypes[44] + mi := &file_bucket_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3185,7 +3241,7 @@ func (x *GetChapterScheduleRequest) String() string { func (*GetChapterScheduleRequest) ProtoMessage() {} func (x *GetChapterScheduleRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[44] + mi := &file_bucket_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3198,7 +3254,7 @@ func (x *GetChapterScheduleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetChapterScheduleRequest.ProtoReflect.Descriptor instead. func (*GetChapterScheduleRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{44} + return file_bucket_proto_rawDescGZIP(), []int{45} } type GetChapterScheduleResponse struct { @@ -3210,7 +3266,7 @@ type GetChapterScheduleResponse struct { func (x *GetChapterScheduleResponse) Reset() { *x = GetChapterScheduleResponse{} - mi := &file_bucket_proto_msgTypes[45] + mi := &file_bucket_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3222,7 +3278,7 @@ func (x *GetChapterScheduleResponse) String() string { func (*GetChapterScheduleResponse) ProtoMessage() {} func (x *GetChapterScheduleResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[45] + mi := &file_bucket_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3235,7 +3291,7 @@ func (x *GetChapterScheduleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetChapterScheduleResponse.ProtoReflect.Descriptor instead. func (*GetChapterScheduleResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{45} + return file_bucket_proto_rawDescGZIP(), []int{46} } func (x *GetChapterScheduleResponse) GetCron() string { @@ -3253,7 +3309,7 @@ type DiscoveryRequest struct { func (x *DiscoveryRequest) Reset() { *x = DiscoveryRequest{} - mi := &file_bucket_proto_msgTypes[46] + mi := &file_bucket_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3265,7 +3321,7 @@ func (x *DiscoveryRequest) String() string { func (*DiscoveryRequest) ProtoMessage() {} func (x *DiscoveryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[46] + mi := &file_bucket_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3278,7 +3334,7 @@ func (x *DiscoveryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryRequest.ProtoReflect.Descriptor instead. func (*DiscoveryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{46} + return file_bucket_proto_rawDescGZIP(), []int{47} } // ServerInfo carries the server build metadata (unauthenticated). @@ -3294,7 +3350,7 @@ type ServerInfo struct { func (x *ServerInfo) Reset() { *x = ServerInfo{} - mi := &file_bucket_proto_msgTypes[47] + mi := &file_bucket_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3306,7 +3362,7 @@ func (x *ServerInfo) String() string { func (*ServerInfo) ProtoMessage() {} func (x *ServerInfo) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[47] + mi := &file_bucket_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3319,7 +3375,7 @@ func (x *ServerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ServerInfo.ProtoReflect.Descriptor instead. func (*ServerInfo) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{47} + return file_bucket_proto_rawDescGZIP(), []int{48} } func (x *ServerInfo) GetVersion() string { @@ -3360,7 +3416,7 @@ type DiscoveryResponse struct { func (x *DiscoveryResponse) Reset() { *x = DiscoveryResponse{} - mi := &file_bucket_proto_msgTypes[48] + mi := &file_bucket_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3372,7 +3428,7 @@ func (x *DiscoveryResponse) String() string { func (*DiscoveryResponse) ProtoMessage() {} func (x *DiscoveryResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[48] + mi := &file_bucket_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3385,7 +3441,7 @@ func (x *DiscoveryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoveryResponse.ProtoReflect.Descriptor instead. func (*DiscoveryResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{48} + return file_bucket_proto_rawDescGZIP(), []int{49} } func (x *DiscoveryResponse) GetResponseSigning() *ResponseSigningInfo { @@ -3413,7 +3469,7 @@ type ResponseSigningInfo struct { func (x *ResponseSigningInfo) Reset() { *x = ResponseSigningInfo{} - mi := &file_bucket_proto_msgTypes[49] + mi := &file_bucket_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3425,7 +3481,7 @@ func (x *ResponseSigningInfo) String() string { func (*ResponseSigningInfo) ProtoMessage() {} func (x *ResponseSigningInfo) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[49] + mi := &file_bucket_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3438,7 +3494,7 @@ func (x *ResponseSigningInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ResponseSigningInfo.ProtoReflect.Descriptor instead. func (*ResponseSigningInfo) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{49} + return file_bucket_proto_rawDescGZIP(), []int{50} } func (x *ResponseSigningInfo) GetPublicKey() []byte { @@ -3473,7 +3529,7 @@ type CreateTransactionPayload struct { func (x *CreateTransactionPayload) Reset() { *x = CreateTransactionPayload{} - mi := &file_bucket_proto_msgTypes[50] + mi := &file_bucket_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3485,7 +3541,7 @@ func (x *CreateTransactionPayload) String() string { func (*CreateTransactionPayload) ProtoMessage() {} func (x *CreateTransactionPayload) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[50] + mi := &file_bucket_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3498,7 +3554,7 @@ func (x *CreateTransactionPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTransactionPayload.ProtoReflect.Descriptor instead. func (*CreateTransactionPayload) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{50} + return file_bucket_proto_rawDescGZIP(), []int{51} } func (x *CreateTransactionPayload) GetPostings() []*commonpb.Posting { @@ -3579,7 +3635,7 @@ type RevertTransactionPayload struct { func (x *RevertTransactionPayload) Reset() { *x = RevertTransactionPayload{} - mi := &file_bucket_proto_msgTypes[51] + mi := &file_bucket_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3591,7 +3647,7 @@ func (x *RevertTransactionPayload) String() string { func (*RevertTransactionPayload) ProtoMessage() {} func (x *RevertTransactionPayload) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[51] + mi := &file_bucket_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3604,7 +3660,7 @@ func (x *RevertTransactionPayload) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertTransactionPayload.ProtoReflect.Descriptor instead. func (*RevertTransactionPayload) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{51} + return file_bucket_proto_rawDescGZIP(), []int{52} } func (x *RevertTransactionPayload) GetTransactionId() uint64 { @@ -3668,7 +3724,7 @@ type LedgerAction struct { func (x *LedgerAction) Reset() { *x = LedgerAction{} - mi := &file_bucket_proto_msgTypes[52] + mi := &file_bucket_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3680,7 +3736,7 @@ func (x *LedgerAction) String() string { func (*LedgerAction) ProtoMessage() {} func (x *LedgerAction) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[52] + mi := &file_bucket_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3693,7 +3749,7 @@ func (x *LedgerAction) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerAction.ProtoReflect.Descriptor instead. func (*LedgerAction) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{52} + return file_bucket_proto_rawDescGZIP(), []int{53} } func (x *LedgerAction) GetData() isLedgerAction_Data { @@ -3822,7 +3878,7 @@ type LedgerApplyRequest struct { func (x *LedgerApplyRequest) Reset() { *x = LedgerApplyRequest{} - mi := &file_bucket_proto_msgTypes[53] + mi := &file_bucket_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3834,7 +3890,7 @@ func (x *LedgerApplyRequest) String() string { func (*LedgerApplyRequest) ProtoMessage() {} func (x *LedgerApplyRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[53] + mi := &file_bucket_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3847,7 +3903,7 @@ func (x *LedgerApplyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerApplyRequest.ProtoReflect.Descriptor instead. func (*LedgerApplyRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{53} + return file_bucket_proto_rawDescGZIP(), []int{54} } func (x *LedgerApplyRequest) GetLedger() string { @@ -3874,7 +3930,7 @@ type AddAccountTypeRequest struct { func (x *AddAccountTypeRequest) Reset() { *x = AddAccountTypeRequest{} - mi := &file_bucket_proto_msgTypes[54] + mi := &file_bucket_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3886,7 +3942,7 @@ func (x *AddAccountTypeRequest) String() string { func (*AddAccountTypeRequest) ProtoMessage() {} func (x *AddAccountTypeRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[54] + mi := &file_bucket_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3899,7 +3955,7 @@ func (x *AddAccountTypeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAccountTypeRequest.ProtoReflect.Descriptor instead. func (*AddAccountTypeRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{54} + return file_bucket_proto_rawDescGZIP(), []int{55} } func (x *AddAccountTypeRequest) GetAccountType() *commonpb.AccountType { @@ -3919,7 +3975,7 @@ type RemoveAccountTypeRequest struct { func (x *RemoveAccountTypeRequest) Reset() { *x = RemoveAccountTypeRequest{} - mi := &file_bucket_proto_msgTypes[55] + mi := &file_bucket_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3931,7 +3987,7 @@ func (x *RemoveAccountTypeRequest) String() string { func (*RemoveAccountTypeRequest) ProtoMessage() {} func (x *RemoveAccountTypeRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[55] + mi := &file_bucket_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3944,7 +4000,7 @@ func (x *RemoveAccountTypeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveAccountTypeRequest.ProtoReflect.Descriptor instead. func (*RemoveAccountTypeRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{55} + return file_bucket_proto_rawDescGZIP(), []int{56} } func (x *RemoveAccountTypeRequest) GetName() string { @@ -3964,7 +4020,7 @@ type SetDefaultEnforcementModeRequest struct { func (x *SetDefaultEnforcementModeRequest) Reset() { *x = SetDefaultEnforcementModeRequest{} - mi := &file_bucket_proto_msgTypes[56] + mi := &file_bucket_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3976,7 +4032,7 @@ func (x *SetDefaultEnforcementModeRequest) String() string { func (*SetDefaultEnforcementModeRequest) ProtoMessage() {} func (x *SetDefaultEnforcementModeRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[56] + mi := &file_bucket_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3989,7 +4045,7 @@ func (x *SetDefaultEnforcementModeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetDefaultEnforcementModeRequest.ProtoReflect.Descriptor instead. func (*SetDefaultEnforcementModeRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{56} + return file_bucket_proto_rawDescGZIP(), []int{57} } func (x *SetDefaultEnforcementModeRequest) GetEnforcementMode() commonpb.ChartEnforcementMode { @@ -4010,7 +4066,7 @@ type SetDefaultEnforcementModeLedgerRequest struct { func (x *SetDefaultEnforcementModeLedgerRequest) Reset() { *x = SetDefaultEnforcementModeLedgerRequest{} - mi := &file_bucket_proto_msgTypes[57] + mi := &file_bucket_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4022,7 +4078,7 @@ func (x *SetDefaultEnforcementModeLedgerRequest) String() string { func (*SetDefaultEnforcementModeLedgerRequest) ProtoMessage() {} func (x *SetDefaultEnforcementModeLedgerRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[57] + mi := &file_bucket_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4035,7 +4091,7 @@ func (x *SetDefaultEnforcementModeLedgerRequest) ProtoReflect() protoreflect.Mes // Deprecated: Use SetDefaultEnforcementModeLedgerRequest.ProtoReflect.Descriptor instead. func (*SetDefaultEnforcementModeLedgerRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{57} + return file_bucket_proto_rawDescGZIP(), []int{58} } func (x *SetDefaultEnforcementModeLedgerRequest) GetLedger() string { @@ -4063,7 +4119,7 @@ type AddAccountTypeLedgerRequest struct { func (x *AddAccountTypeLedgerRequest) Reset() { *x = AddAccountTypeLedgerRequest{} - mi := &file_bucket_proto_msgTypes[58] + mi := &file_bucket_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4075,7 +4131,7 @@ func (x *AddAccountTypeLedgerRequest) String() string { func (*AddAccountTypeLedgerRequest) ProtoMessage() {} func (x *AddAccountTypeLedgerRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[58] + mi := &file_bucket_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4088,7 +4144,7 @@ func (x *AddAccountTypeLedgerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddAccountTypeLedgerRequest.ProtoReflect.Descriptor instead. func (*AddAccountTypeLedgerRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{58} + return file_bucket_proto_rawDescGZIP(), []int{59} } func (x *AddAccountTypeLedgerRequest) GetLedger() string { @@ -4116,7 +4172,7 @@ type RemoveAccountTypeLedgerRequest struct { func (x *RemoveAccountTypeLedgerRequest) Reset() { *x = RemoveAccountTypeLedgerRequest{} - mi := &file_bucket_proto_msgTypes[59] + mi := &file_bucket_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4128,7 +4184,7 @@ func (x *RemoveAccountTypeLedgerRequest) String() string { func (*RemoveAccountTypeLedgerRequest) ProtoMessage() {} func (x *RemoveAccountTypeLedgerRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[59] + mi := &file_bucket_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4141,7 +4197,7 @@ func (x *RemoveAccountTypeLedgerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveAccountTypeLedgerRequest.ProtoReflect.Descriptor instead. func (*RemoveAccountTypeLedgerRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{59} + return file_bucket_proto_rawDescGZIP(), []int{60} } func (x *RemoveAccountTypeLedgerRequest) GetLedger() string { @@ -4168,7 +4224,7 @@ type GetPrimaryMetricsRequest struct { func (x *GetPrimaryMetricsRequest) Reset() { *x = GetPrimaryMetricsRequest{} - mi := &file_bucket_proto_msgTypes[60] + mi := &file_bucket_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4180,7 +4236,7 @@ func (x *GetPrimaryMetricsRequest) String() string { func (*GetPrimaryMetricsRequest) ProtoMessage() {} func (x *GetPrimaryMetricsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[60] + mi := &file_bucket_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4193,7 +4249,7 @@ func (x *GetPrimaryMetricsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPrimaryMetricsRequest.ProtoReflect.Descriptor instead. func (*GetPrimaryMetricsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{60} + return file_bucket_proto_rawDescGZIP(), []int{61} } func (x *GetPrimaryMetricsRequest) GetNodeId() uint32 { @@ -4215,7 +4271,7 @@ type GetPrimaryMetricsResponse struct { func (x *GetPrimaryMetricsResponse) Reset() { *x = GetPrimaryMetricsResponse{} - mi := &file_bucket_proto_msgTypes[61] + mi := &file_bucket_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4227,7 +4283,7 @@ func (x *GetPrimaryMetricsResponse) String() string { func (*GetPrimaryMetricsResponse) ProtoMessage() {} func (x *GetPrimaryMetricsResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[61] + mi := &file_bucket_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4240,7 +4296,7 @@ func (x *GetPrimaryMetricsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetPrimaryMetricsResponse.ProtoReflect.Descriptor instead. func (*GetPrimaryMetricsResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{61} + return file_bucket_proto_rawDescGZIP(), []int{62} } func (x *GetPrimaryMetricsResponse) GetAvailable() bool { @@ -4267,7 +4323,7 @@ type GetSecondaryMetricsRequest struct { func (x *GetSecondaryMetricsRequest) Reset() { *x = GetSecondaryMetricsRequest{} - mi := &file_bucket_proto_msgTypes[62] + mi := &file_bucket_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4279,7 +4335,7 @@ func (x *GetSecondaryMetricsRequest) String() string { func (*GetSecondaryMetricsRequest) ProtoMessage() {} func (x *GetSecondaryMetricsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[62] + mi := &file_bucket_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4292,7 +4348,7 @@ func (x *GetSecondaryMetricsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSecondaryMetricsRequest.ProtoReflect.Descriptor instead. func (*GetSecondaryMetricsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{62} + return file_bucket_proto_rawDescGZIP(), []int{63} } func (x *GetSecondaryMetricsRequest) GetNodeId() uint32 { @@ -4314,7 +4370,7 @@ type GetSecondaryMetricsResponse struct { func (x *GetSecondaryMetricsResponse) Reset() { *x = GetSecondaryMetricsResponse{} - mi := &file_bucket_proto_msgTypes[63] + mi := &file_bucket_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4326,7 +4382,7 @@ func (x *GetSecondaryMetricsResponse) String() string { func (*GetSecondaryMetricsResponse) ProtoMessage() {} func (x *GetSecondaryMetricsResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[63] + mi := &file_bucket_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4339,7 +4395,7 @@ func (x *GetSecondaryMetricsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSecondaryMetricsResponse.ProtoReflect.Descriptor instead. func (*GetSecondaryMetricsResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{63} + return file_bucket_proto_rawDescGZIP(), []int{64} } func (x *GetSecondaryMetricsResponse) GetAvailable() bool { @@ -4376,7 +4432,7 @@ type PebbleMetrics struct { func (x *PebbleMetrics) Reset() { *x = PebbleMetrics{} - mi := &file_bucket_proto_msgTypes[64] + mi := &file_bucket_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4388,7 +4444,7 @@ func (x *PebbleMetrics) String() string { func (*PebbleMetrics) ProtoMessage() {} func (x *PebbleMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[64] + mi := &file_bucket_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4401,7 +4457,7 @@ func (x *PebbleMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use PebbleMetrics.ProtoReflect.Descriptor instead. func (*PebbleMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{64} + return file_bucket_proto_rawDescGZIP(), []int{65} } func (x *PebbleMetrics) GetBlockCache() *BlockCacheMetrics { @@ -4493,7 +4549,7 @@ type BlockCacheMetrics struct { func (x *BlockCacheMetrics) Reset() { *x = BlockCacheMetrics{} - mi := &file_bucket_proto_msgTypes[65] + mi := &file_bucket_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4505,7 +4561,7 @@ func (x *BlockCacheMetrics) String() string { func (*BlockCacheMetrics) ProtoMessage() {} func (x *BlockCacheMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[65] + mi := &file_bucket_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4518,7 +4574,7 @@ func (x *BlockCacheMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockCacheMetrics.ProtoReflect.Descriptor instead. func (*BlockCacheMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{65} + return file_bucket_proto_rawDescGZIP(), []int{66} } func (x *BlockCacheMetrics) GetSize() int64 { @@ -4569,7 +4625,7 @@ type CompactMetrics struct { func (x *CompactMetrics) Reset() { *x = CompactMetrics{} - mi := &file_bucket_proto_msgTypes[66] + mi := &file_bucket_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4581,7 +4637,7 @@ func (x *CompactMetrics) String() string { func (*CompactMetrics) ProtoMessage() {} func (x *CompactMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[66] + mi := &file_bucket_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4594,7 +4650,7 @@ func (x *CompactMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use CompactMetrics.ProtoReflect.Descriptor instead. func (*CompactMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{66} + return file_bucket_proto_rawDescGZIP(), []int{67} } func (x *CompactMetrics) GetCount() int64 { @@ -4694,7 +4750,7 @@ type FlushMetrics struct { func (x *FlushMetrics) Reset() { *x = FlushMetrics{} - mi := &file_bucket_proto_msgTypes[67] + mi := &file_bucket_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4706,7 +4762,7 @@ func (x *FlushMetrics) String() string { func (*FlushMetrics) ProtoMessage() {} func (x *FlushMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[67] + mi := &file_bucket_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4719,7 +4775,7 @@ func (x *FlushMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use FlushMetrics.ProtoReflect.Descriptor instead. func (*FlushMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{67} + return file_bucket_proto_rawDescGZIP(), []int{68} } func (x *FlushMetrics) GetCount() int64 { @@ -4769,7 +4825,7 @@ type MemTableMetrics struct { func (x *MemTableMetrics) Reset() { *x = MemTableMetrics{} - mi := &file_bucket_proto_msgTypes[68] + mi := &file_bucket_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4781,7 +4837,7 @@ func (x *MemTableMetrics) String() string { func (*MemTableMetrics) ProtoMessage() {} func (x *MemTableMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[68] + mi := &file_bucket_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4794,7 +4850,7 @@ func (x *MemTableMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use MemTableMetrics.ProtoReflect.Descriptor instead. func (*MemTableMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{68} + return file_bucket_proto_rawDescGZIP(), []int{69} } func (x *MemTableMetrics) GetSize() uint64 { @@ -4837,7 +4893,7 @@ type SnapshotsMetrics struct { func (x *SnapshotsMetrics) Reset() { *x = SnapshotsMetrics{} - mi := &file_bucket_proto_msgTypes[69] + mi := &file_bucket_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4849,7 +4905,7 @@ func (x *SnapshotsMetrics) String() string { func (*SnapshotsMetrics) ProtoMessage() {} func (x *SnapshotsMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[69] + mi := &file_bucket_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4862,7 +4918,7 @@ func (x *SnapshotsMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotsMetrics.ProtoReflect.Descriptor instead. func (*SnapshotsMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{69} + return file_bucket_proto_rawDescGZIP(), []int{70} } func (x *SnapshotsMetrics) GetCount() int32 { @@ -4905,7 +4961,7 @@ type TableMetrics struct { func (x *TableMetrics) Reset() { *x = TableMetrics{} - mi := &file_bucket_proto_msgTypes[70] + mi := &file_bucket_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4917,7 +4973,7 @@ func (x *TableMetrics) String() string { func (*TableMetrics) ProtoMessage() {} func (x *TableMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[70] + mi := &file_bucket_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4930,7 +4986,7 @@ func (x *TableMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use TableMetrics.ProtoReflect.Descriptor instead. func (*TableMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{70} + return file_bucket_proto_rawDescGZIP(), []int{71} } func (x *TableMetrics) GetZombieSize() uint64 { @@ -4973,7 +5029,7 @@ type TableCacheMetrics struct { func (x *TableCacheMetrics) Reset() { *x = TableCacheMetrics{} - mi := &file_bucket_proto_msgTypes[71] + mi := &file_bucket_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4985,7 +5041,7 @@ func (x *TableCacheMetrics) String() string { func (*TableCacheMetrics) ProtoMessage() {} func (x *TableCacheMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[71] + mi := &file_bucket_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4998,7 +5054,7 @@ func (x *TableCacheMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use TableCacheMetrics.ProtoReflect.Descriptor instead. func (*TableCacheMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{71} + return file_bucket_proto_rawDescGZIP(), []int{72} } func (x *TableCacheMetrics) GetSize() int64 { @@ -5042,7 +5098,7 @@ type WALMetrics struct { func (x *WALMetrics) Reset() { *x = WALMetrics{} - mi := &file_bucket_proto_msgTypes[72] + mi := &file_bucket_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5054,7 +5110,7 @@ func (x *WALMetrics) String() string { func (*WALMetrics) ProtoMessage() {} func (x *WALMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[72] + mi := &file_bucket_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5067,7 +5123,7 @@ func (x *WALMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use WALMetrics.ProtoReflect.Descriptor instead. func (*WALMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{72} + return file_bucket_proto_rawDescGZIP(), []int{73} } func (x *WALMetrics) GetFiles() int64 { @@ -5115,7 +5171,7 @@ type KeysMetrics struct { func (x *KeysMetrics) Reset() { *x = KeysMetrics{} - mi := &file_bucket_proto_msgTypes[73] + mi := &file_bucket_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5127,7 +5183,7 @@ func (x *KeysMetrics) String() string { func (*KeysMetrics) ProtoMessage() {} func (x *KeysMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[73] + mi := &file_bucket_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5140,7 +5196,7 @@ func (x *KeysMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use KeysMetrics.ProtoReflect.Descriptor instead. func (*KeysMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{73} + return file_bucket_proto_rawDescGZIP(), []int{74} } func (x *KeysMetrics) GetRangeKeySetsCount() uint64 { @@ -5179,7 +5235,7 @@ type LevelMetrics struct { func (x *LevelMetrics) Reset() { *x = LevelMetrics{} - mi := &file_bucket_proto_msgTypes[74] + mi := &file_bucket_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5191,7 +5247,7 @@ func (x *LevelMetrics) String() string { func (*LevelMetrics) ProtoMessage() {} func (x *LevelMetrics) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[74] + mi := &file_bucket_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5204,7 +5260,7 @@ func (x *LevelMetrics) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelMetrics.ProtoReflect.Descriptor instead. func (*LevelMetrics) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{74} + return file_bucket_proto_rawDescGZIP(), []int{75} } func (x *LevelMetrics) GetLevel() int32 { @@ -5313,7 +5369,7 @@ type CheckStoreRequest struct { func (x *CheckStoreRequest) Reset() { *x = CheckStoreRequest{} - mi := &file_bucket_proto_msgTypes[75] + mi := &file_bucket_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5325,7 +5381,7 @@ func (x *CheckStoreRequest) String() string { func (*CheckStoreRequest) ProtoMessage() {} func (x *CheckStoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[75] + mi := &file_bucket_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5338,7 +5394,7 @@ func (x *CheckStoreRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckStoreRequest.ProtoReflect.Descriptor instead. func (*CheckStoreRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{75} + return file_bucket_proto_rawDescGZIP(), []int{76} } type CheckStoreEvent struct { @@ -5354,7 +5410,7 @@ type CheckStoreEvent struct { func (x *CheckStoreEvent) Reset() { *x = CheckStoreEvent{} - mi := &file_bucket_proto_msgTypes[76] + mi := &file_bucket_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5366,7 +5422,7 @@ func (x *CheckStoreEvent) String() string { func (*CheckStoreEvent) ProtoMessage() {} func (x *CheckStoreEvent) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[76] + mi := &file_bucket_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5379,7 +5435,7 @@ func (x *CheckStoreEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckStoreEvent.ProtoReflect.Descriptor instead. func (*CheckStoreEvent) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{76} + return file_bucket_proto_rawDescGZIP(), []int{77} } func (x *CheckStoreEvent) GetType() isCheckStoreEvent_Type { @@ -5438,7 +5494,7 @@ type CheckStoreError struct { func (x *CheckStoreError) Reset() { *x = CheckStoreError{} - mi := &file_bucket_proto_msgTypes[77] + mi := &file_bucket_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5450,7 +5506,7 @@ func (x *CheckStoreError) String() string { func (*CheckStoreError) ProtoMessage() {} func (x *CheckStoreError) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[77] + mi := &file_bucket_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5463,7 +5519,7 @@ func (x *CheckStoreError) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckStoreError.ProtoReflect.Descriptor instead. func (*CheckStoreError) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{77} + return file_bucket_proto_rawDescGZIP(), []int{78} } func (x *CheckStoreError) GetErrorType() CheckStoreErrorType { @@ -5525,7 +5581,7 @@ type CheckStoreProgress struct { func (x *CheckStoreProgress) Reset() { *x = CheckStoreProgress{} - mi := &file_bucket_proto_msgTypes[78] + mi := &file_bucket_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5537,7 +5593,7 @@ func (x *CheckStoreProgress) String() string { func (*CheckStoreProgress) ProtoMessage() {} func (x *CheckStoreProgress) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[78] + mi := &file_bucket_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5550,7 +5606,7 @@ func (x *CheckStoreProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckStoreProgress.ProtoReflect.Descriptor instead. func (*CheckStoreProgress) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{78} + return file_bucket_proto_rawDescGZIP(), []int{79} } func (x *CheckStoreProgress) GetLogsChecked() uint64 { @@ -5580,7 +5636,7 @@ type ListAuditEntriesRequest struct { func (x *ListAuditEntriesRequest) Reset() { *x = ListAuditEntriesRequest{} - mi := &file_bucket_proto_msgTypes[79] + mi := &file_bucket_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5592,7 +5648,7 @@ func (x *ListAuditEntriesRequest) String() string { func (*ListAuditEntriesRequest) ProtoMessage() {} func (x *ListAuditEntriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[79] + mi := &file_bucket_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5605,7 +5661,7 @@ func (x *ListAuditEntriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAuditEntriesRequest.ProtoReflect.Descriptor instead. func (*ListAuditEntriesRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{79} + return file_bucket_proto_rawDescGZIP(), []int{80} } func (x *ListAuditEntriesRequest) GetOptions() *commonpb.ListOptions { @@ -5624,7 +5680,7 @@ type GetAuditEntryRequest struct { func (x *GetAuditEntryRequest) Reset() { *x = GetAuditEntryRequest{} - mi := &file_bucket_proto_msgTypes[80] + mi := &file_bucket_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5636,7 +5692,7 @@ func (x *GetAuditEntryRequest) String() string { func (*GetAuditEntryRequest) ProtoMessage() {} func (x *GetAuditEntryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[80] + mi := &file_bucket_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5649,7 +5705,7 @@ func (x *GetAuditEntryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAuditEntryRequest.ProtoReflect.Descriptor instead. func (*GetAuditEntryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{80} + return file_bucket_proto_rawDescGZIP(), []int{81} } func (x *GetAuditEntryRequest) GetSequence() uint64 { @@ -5670,7 +5726,7 @@ type ListLogsRequest struct { func (x *ListLogsRequest) Reset() { *x = ListLogsRequest{} - mi := &file_bucket_proto_msgTypes[81] + mi := &file_bucket_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5682,7 +5738,7 @@ func (x *ListLogsRequest) String() string { func (*ListLogsRequest) ProtoMessage() {} func (x *ListLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[81] + mi := &file_bucket_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5695,7 +5751,7 @@ func (x *ListLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLogsRequest.ProtoReflect.Descriptor instead. func (*ListLogsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{81} + return file_bucket_proto_rawDescGZIP(), []int{82} } func (x *ListLogsRequest) GetLedger() string { @@ -5723,7 +5779,7 @@ type GetLogRequest struct { func (x *GetLogRequest) Reset() { *x = GetLogRequest{} - mi := &file_bucket_proto_msgTypes[82] + mi := &file_bucket_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5735,7 +5791,7 @@ func (x *GetLogRequest) String() string { func (*GetLogRequest) ProtoMessage() {} func (x *GetLogRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[82] + mi := &file_bucket_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5748,7 +5804,7 @@ func (x *GetLogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLogRequest.ProtoReflect.Descriptor instead. func (*GetLogRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{82} + return file_bucket_proto_rawDescGZIP(), []int{83} } func (x *GetLogRequest) GetSequence() uint64 { @@ -5773,7 +5829,7 @@ type GetEventsSinksRequest struct { func (x *GetEventsSinksRequest) Reset() { *x = GetEventsSinksRequest{} - mi := &file_bucket_proto_msgTypes[83] + mi := &file_bucket_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5785,7 +5841,7 @@ func (x *GetEventsSinksRequest) String() string { func (*GetEventsSinksRequest) ProtoMessage() {} func (x *GetEventsSinksRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[83] + mi := &file_bucket_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5798,7 +5854,7 @@ func (x *GetEventsSinksRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEventsSinksRequest.ProtoReflect.Descriptor instead. func (*GetEventsSinksRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{83} + return file_bucket_proto_rawDescGZIP(), []int{84} } type GetEventsSinksResponse struct { @@ -5811,7 +5867,7 @@ type GetEventsSinksResponse struct { func (x *GetEventsSinksResponse) Reset() { *x = GetEventsSinksResponse{} - mi := &file_bucket_proto_msgTypes[84] + mi := &file_bucket_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5823,7 +5879,7 @@ func (x *GetEventsSinksResponse) String() string { func (*GetEventsSinksResponse) ProtoMessage() {} func (x *GetEventsSinksResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[84] + mi := &file_bucket_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5836,7 +5892,7 @@ func (x *GetEventsSinksResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEventsSinksResponse.ProtoReflect.Descriptor instead. func (*GetEventsSinksResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{84} + return file_bucket_proto_rawDescGZIP(), []int{85} } func (x *GetEventsSinksResponse) GetSinks() []*commonpb.SinkConfig { @@ -5862,7 +5918,7 @@ type GetMetadataSchemaStatusRequest struct { func (x *GetMetadataSchemaStatusRequest) Reset() { *x = GetMetadataSchemaStatusRequest{} - mi := &file_bucket_proto_msgTypes[85] + mi := &file_bucket_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5874,7 +5930,7 @@ func (x *GetMetadataSchemaStatusRequest) String() string { func (*GetMetadataSchemaStatusRequest) ProtoMessage() {} func (x *GetMetadataSchemaStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[85] + mi := &file_bucket_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5887,7 +5943,7 @@ func (x *GetMetadataSchemaStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMetadataSchemaStatusRequest.ProtoReflect.Descriptor instead. func (*GetMetadataSchemaStatusRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{85} + return file_bucket_proto_rawDescGZIP(), []int{86} } func (x *GetMetadataSchemaStatusRequest) GetLedger() string { @@ -5908,7 +5964,7 @@ type GetMetadataSchemaStatusResponse struct { func (x *GetMetadataSchemaStatusResponse) Reset() { *x = GetMetadataSchemaStatusResponse{} - mi := &file_bucket_proto_msgTypes[86] + mi := &file_bucket_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5920,7 +5976,7 @@ func (x *GetMetadataSchemaStatusResponse) String() string { func (*GetMetadataSchemaStatusResponse) ProtoMessage() {} func (x *GetMetadataSchemaStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[86] + mi := &file_bucket_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5933,7 +5989,7 @@ func (x *GetMetadataSchemaStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetMetadataSchemaStatusResponse.ProtoReflect.Descriptor instead. func (*GetMetadataSchemaStatusResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{86} + return file_bucket_proto_rawDescGZIP(), []int{87} } func (x *GetMetadataSchemaStatusResponse) GetAccountFields() map[string]*MetadataFieldStatus { @@ -5966,7 +6022,7 @@ type MetadataFieldStatus struct { func (x *MetadataFieldStatus) Reset() { *x = MetadataFieldStatus{} - mi := &file_bucket_proto_msgTypes[87] + mi := &file_bucket_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5978,7 +6034,7 @@ func (x *MetadataFieldStatus) String() string { func (*MetadataFieldStatus) ProtoMessage() {} func (x *MetadataFieldStatus) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[87] + mi := &file_bucket_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5991,7 +6047,7 @@ func (x *MetadataFieldStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use MetadataFieldStatus.ProtoReflect.Descriptor instead. func (*MetadataFieldStatus) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{87} + return file_bucket_proto_rawDescGZIP(), []int{88} } func (x *MetadataFieldStatus) GetDeclaredType() commonpb.MetadataType { @@ -6013,7 +6069,7 @@ type AnalyzeAccountsRequest struct { func (x *AnalyzeAccountsRequest) Reset() { *x = AnalyzeAccountsRequest{} - mi := &file_bucket_proto_msgTypes[88] + mi := &file_bucket_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6025,7 +6081,7 @@ func (x *AnalyzeAccountsRequest) String() string { func (*AnalyzeAccountsRequest) ProtoMessage() {} func (x *AnalyzeAccountsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[88] + mi := &file_bucket_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6038,7 +6094,7 @@ func (x *AnalyzeAccountsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeAccountsRequest.ProtoReflect.Descriptor instead. func (*AnalyzeAccountsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{88} + return file_bucket_proto_rawDescGZIP(), []int{89} } func (x *AnalyzeAccountsRequest) GetLedger() string { @@ -6065,7 +6121,7 @@ type AnalyzeAccountsResponse struct { func (x *AnalyzeAccountsResponse) Reset() { *x = AnalyzeAccountsResponse{} - mi := &file_bucket_proto_msgTypes[89] + mi := &file_bucket_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6077,7 +6133,7 @@ func (x *AnalyzeAccountsResponse) String() string { func (*AnalyzeAccountsResponse) ProtoMessage() {} func (x *AnalyzeAccountsResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[89] + mi := &file_bucket_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6090,7 +6146,7 @@ func (x *AnalyzeAccountsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeAccountsResponse.ProtoReflect.Descriptor instead. func (*AnalyzeAccountsResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{89} + return file_bucket_proto_rawDescGZIP(), []int{90} } func (x *AnalyzeAccountsResponse) GetPatterns() []*AccountPattern { @@ -6119,7 +6175,7 @@ type AnalyzeProgress struct { func (x *AnalyzeProgress) Reset() { *x = AnalyzeProgress{} - mi := &file_bucket_proto_msgTypes[90] + mi := &file_bucket_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6131,7 +6187,7 @@ func (x *AnalyzeProgress) String() string { func (*AnalyzeProgress) ProtoMessage() {} func (x *AnalyzeProgress) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[90] + mi := &file_bucket_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6144,7 +6200,7 @@ func (x *AnalyzeProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeProgress.ProtoReflect.Descriptor instead. func (*AnalyzeProgress) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{90} + return file_bucket_proto_rawDescGZIP(), []int{91} } func (x *AnalyzeProgress) GetProcessed() uint64 { @@ -6182,7 +6238,7 @@ type AnalyzeAccountsEvent struct { func (x *AnalyzeAccountsEvent) Reset() { *x = AnalyzeAccountsEvent{} - mi := &file_bucket_proto_msgTypes[91] + mi := &file_bucket_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6194,7 +6250,7 @@ func (x *AnalyzeAccountsEvent) String() string { func (*AnalyzeAccountsEvent) ProtoMessage() {} func (x *AnalyzeAccountsEvent) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[91] + mi := &file_bucket_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6207,7 +6263,7 @@ func (x *AnalyzeAccountsEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeAccountsEvent.ProtoReflect.Descriptor instead. func (*AnalyzeAccountsEvent) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{91} + return file_bucket_proto_rawDescGZIP(), []int{92} } func (x *AnalyzeAccountsEvent) GetType() isAnalyzeAccountsEvent_Type { @@ -6265,7 +6321,7 @@ type AnalyzeTransactionsEvent struct { func (x *AnalyzeTransactionsEvent) Reset() { *x = AnalyzeTransactionsEvent{} - mi := &file_bucket_proto_msgTypes[92] + mi := &file_bucket_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6277,7 +6333,7 @@ func (x *AnalyzeTransactionsEvent) String() string { func (*AnalyzeTransactionsEvent) ProtoMessage() {} func (x *AnalyzeTransactionsEvent) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[92] + mi := &file_bucket_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6290,7 +6346,7 @@ func (x *AnalyzeTransactionsEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeTransactionsEvent.ProtoReflect.Descriptor instead. func (*AnalyzeTransactionsEvent) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{92} + return file_bucket_proto_rawDescGZIP(), []int{93} } func (x *AnalyzeTransactionsEvent) GetType() isAnalyzeTransactionsEvent_Type { @@ -6348,7 +6404,7 @@ type AccountPattern struct { func (x *AccountPattern) Reset() { *x = AccountPattern{} - mi := &file_bucket_proto_msgTypes[93] + mi := &file_bucket_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6360,7 +6416,7 @@ func (x *AccountPattern) String() string { func (*AccountPattern) ProtoMessage() {} func (x *AccountPattern) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[93] + mi := &file_bucket_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6373,7 +6429,7 @@ func (x *AccountPattern) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountPattern.ProtoReflect.Descriptor instead. func (*AccountPattern) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{93} + return file_bucket_proto_rawDescGZIP(), []int{94} } func (x *AccountPattern) GetPattern() string { @@ -6427,7 +6483,7 @@ type PatternSegment struct { func (x *PatternSegment) Reset() { *x = PatternSegment{} - mi := &file_bucket_proto_msgTypes[94] + mi := &file_bucket_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6439,7 +6495,7 @@ func (x *PatternSegment) String() string { func (*PatternSegment) ProtoMessage() {} func (x *PatternSegment) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[94] + mi := &file_bucket_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6452,7 +6508,7 @@ func (x *PatternSegment) ProtoReflect() protoreflect.Message { // Deprecated: Use PatternSegment.ProtoReflect.Descriptor instead. func (*PatternSegment) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{94} + return file_bucket_proto_rawDescGZIP(), []int{95} } func (x *PatternSegment) GetPosition() uint32 { @@ -6516,7 +6572,7 @@ type AnalyzeTransactionsRequest struct { func (x *AnalyzeTransactionsRequest) Reset() { *x = AnalyzeTransactionsRequest{} - mi := &file_bucket_proto_msgTypes[95] + mi := &file_bucket_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6528,7 +6584,7 @@ func (x *AnalyzeTransactionsRequest) String() string { func (*AnalyzeTransactionsRequest) ProtoMessage() {} func (x *AnalyzeTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[95] + mi := &file_bucket_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6541,7 +6597,7 @@ func (x *AnalyzeTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeTransactionsRequest.ProtoReflect.Descriptor instead. func (*AnalyzeTransactionsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{95} + return file_bucket_proto_rawDescGZIP(), []int{96} } func (x *AnalyzeTransactionsRequest) GetLedger() string { @@ -6569,7 +6625,7 @@ type AnalyzeTransactionsResponse struct { func (x *AnalyzeTransactionsResponse) Reset() { *x = AnalyzeTransactionsResponse{} - mi := &file_bucket_proto_msgTypes[96] + mi := &file_bucket_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6581,7 +6637,7 @@ func (x *AnalyzeTransactionsResponse) String() string { func (*AnalyzeTransactionsResponse) ProtoMessage() {} func (x *AnalyzeTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[96] + mi := &file_bucket_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6594,7 +6650,7 @@ func (x *AnalyzeTransactionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AnalyzeTransactionsResponse.ProtoReflect.Descriptor instead. func (*AnalyzeTransactionsResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{96} + return file_bucket_proto_rawDescGZIP(), []int{97} } func (x *AnalyzeTransactionsResponse) GetFlowPatterns() []*FlowPattern { @@ -6633,7 +6689,7 @@ type FlowPattern struct { func (x *FlowPattern) Reset() { *x = FlowPattern{} - mi := &file_bucket_proto_msgTypes[97] + mi := &file_bucket_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6645,7 +6701,7 @@ func (x *FlowPattern) String() string { func (*FlowPattern) ProtoMessage() {} func (x *FlowPattern) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[97] + mi := &file_bucket_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6658,7 +6714,7 @@ func (x *FlowPattern) ProtoReflect() protoreflect.Message { // Deprecated: Use FlowPattern.ProtoReflect.Descriptor instead. func (*FlowPattern) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{97} + return file_bucket_proto_rawDescGZIP(), []int{98} } func (x *FlowPattern) GetSignature() string { @@ -6721,7 +6777,7 @@ type NormalizedPosting struct { func (x *NormalizedPosting) Reset() { *x = NormalizedPosting{} - mi := &file_bucket_proto_msgTypes[98] + mi := &file_bucket_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6733,7 +6789,7 @@ func (x *NormalizedPosting) String() string { func (*NormalizedPosting) ProtoMessage() {} func (x *NormalizedPosting) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[98] + mi := &file_bucket_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6746,7 +6802,7 @@ func (x *NormalizedPosting) ProtoReflect() protoreflect.Message { // Deprecated: Use NormalizedPosting.ProtoReflect.Descriptor instead. func (*NormalizedPosting) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{98} + return file_bucket_proto_rawDescGZIP(), []int{99} } func (x *NormalizedPosting) GetSourcePattern() string { @@ -6782,7 +6838,7 @@ type TemporalStats struct { func (x *TemporalStats) Reset() { *x = TemporalStats{} - mi := &file_bucket_proto_msgTypes[99] + mi := &file_bucket_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6794,7 +6850,7 @@ func (x *TemporalStats) String() string { func (*TemporalStats) ProtoMessage() {} func (x *TemporalStats) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[99] + mi := &file_bucket_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6807,7 +6863,7 @@ func (x *TemporalStats) ProtoReflect() protoreflect.Message { // Deprecated: Use TemporalStats.ProtoReflect.Descriptor instead. func (*TemporalStats) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{99} + return file_bucket_proto_rawDescGZIP(), []int{100} } func (x *TemporalStats) GetFirstSeen() *commonpb.Timestamp { @@ -6848,7 +6904,7 @@ type HourBucket struct { func (x *HourBucket) Reset() { *x = HourBucket{} - mi := &file_bucket_proto_msgTypes[100] + mi := &file_bucket_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6860,7 +6916,7 @@ func (x *HourBucket) String() string { func (*HourBucket) ProtoMessage() {} func (x *HourBucket) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[100] + mi := &file_bucket_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6873,7 +6929,7 @@ func (x *HourBucket) ProtoReflect() protoreflect.Message { // Deprecated: Use HourBucket.ProtoReflect.Descriptor instead. func (*HourBucket) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{100} + return file_bucket_proto_rawDescGZIP(), []int{101} } func (x *HourBucket) GetHour() uint32 { @@ -6904,7 +6960,7 @@ type AssetVolumeStats struct { func (x *AssetVolumeStats) Reset() { *x = AssetVolumeStats{} - mi := &file_bucket_proto_msgTypes[101] + mi := &file_bucket_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6916,7 +6972,7 @@ func (x *AssetVolumeStats) String() string { func (*AssetVolumeStats) ProtoMessage() {} func (x *AssetVolumeStats) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[101] + mi := &file_bucket_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6929,7 +6985,7 @@ func (x *AssetVolumeStats) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetVolumeStats.ProtoReflect.Descriptor instead. func (*AssetVolumeStats) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{101} + return file_bucket_proto_rawDescGZIP(), []int{102} } func (x *AssetVolumeStats) GetAsset() string { @@ -6984,7 +7040,7 @@ type CreatePreparedQueryRequest struct { func (x *CreatePreparedQueryRequest) Reset() { *x = CreatePreparedQueryRequest{} - mi := &file_bucket_proto_msgTypes[102] + mi := &file_bucket_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6996,7 +7052,7 @@ func (x *CreatePreparedQueryRequest) String() string { func (*CreatePreparedQueryRequest) ProtoMessage() {} func (x *CreatePreparedQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[102] + mi := &file_bucket_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7009,7 +7065,7 @@ func (x *CreatePreparedQueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePreparedQueryRequest.ProtoReflect.Descriptor instead. func (*CreatePreparedQueryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{102} + return file_bucket_proto_rawDescGZIP(), []int{103} } func (x *CreatePreparedQueryRequest) GetLedger() string { @@ -7034,7 +7090,7 @@ type CreatePreparedQueryResponse struct { func (x *CreatePreparedQueryResponse) Reset() { *x = CreatePreparedQueryResponse{} - mi := &file_bucket_proto_msgTypes[103] + mi := &file_bucket_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7046,7 +7102,7 @@ func (x *CreatePreparedQueryResponse) String() string { func (*CreatePreparedQueryResponse) ProtoMessage() {} func (x *CreatePreparedQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[103] + mi := &file_bucket_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7059,7 +7115,7 @@ func (x *CreatePreparedQueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreatePreparedQueryResponse.ProtoReflect.Descriptor instead. func (*CreatePreparedQueryResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{103} + return file_bucket_proto_rawDescGZIP(), []int{104} } type UpdatePreparedQueryRequest struct { @@ -7073,7 +7129,7 @@ type UpdatePreparedQueryRequest struct { func (x *UpdatePreparedQueryRequest) Reset() { *x = UpdatePreparedQueryRequest{} - mi := &file_bucket_proto_msgTypes[104] + mi := &file_bucket_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7085,7 +7141,7 @@ func (x *UpdatePreparedQueryRequest) String() string { func (*UpdatePreparedQueryRequest) ProtoMessage() {} func (x *UpdatePreparedQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[104] + mi := &file_bucket_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7098,7 +7154,7 @@ func (x *UpdatePreparedQueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatePreparedQueryRequest.ProtoReflect.Descriptor instead. func (*UpdatePreparedQueryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{104} + return file_bucket_proto_rawDescGZIP(), []int{105} } func (x *UpdatePreparedQueryRequest) GetLedger() string { @@ -7130,7 +7186,7 @@ type UpdatePreparedQueryResponse struct { func (x *UpdatePreparedQueryResponse) Reset() { *x = UpdatePreparedQueryResponse{} - mi := &file_bucket_proto_msgTypes[105] + mi := &file_bucket_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7142,7 +7198,7 @@ func (x *UpdatePreparedQueryResponse) String() string { func (*UpdatePreparedQueryResponse) ProtoMessage() {} func (x *UpdatePreparedQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[105] + mi := &file_bucket_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7155,7 +7211,7 @@ func (x *UpdatePreparedQueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdatePreparedQueryResponse.ProtoReflect.Descriptor instead. func (*UpdatePreparedQueryResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{105} + return file_bucket_proto_rawDescGZIP(), []int{106} } type DeletePreparedQueryRequest struct { @@ -7168,7 +7224,7 @@ type DeletePreparedQueryRequest struct { func (x *DeletePreparedQueryRequest) Reset() { *x = DeletePreparedQueryRequest{} - mi := &file_bucket_proto_msgTypes[106] + mi := &file_bucket_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7180,7 +7236,7 @@ func (x *DeletePreparedQueryRequest) String() string { func (*DeletePreparedQueryRequest) ProtoMessage() {} func (x *DeletePreparedQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[106] + mi := &file_bucket_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7193,7 +7249,7 @@ func (x *DeletePreparedQueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePreparedQueryRequest.ProtoReflect.Descriptor instead. func (*DeletePreparedQueryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{106} + return file_bucket_proto_rawDescGZIP(), []int{107} } func (x *DeletePreparedQueryRequest) GetLedger() string { @@ -7218,7 +7274,7 @@ type DeletePreparedQueryResponse struct { func (x *DeletePreparedQueryResponse) Reset() { *x = DeletePreparedQueryResponse{} - mi := &file_bucket_proto_msgTypes[107] + mi := &file_bucket_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7230,7 +7286,7 @@ func (x *DeletePreparedQueryResponse) String() string { func (*DeletePreparedQueryResponse) ProtoMessage() {} func (x *DeletePreparedQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[107] + mi := &file_bucket_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7243,7 +7299,7 @@ func (x *DeletePreparedQueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeletePreparedQueryResponse.ProtoReflect.Descriptor instead. func (*DeletePreparedQueryResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{107} + return file_bucket_proto_rawDescGZIP(), []int{108} } type ListPreparedQueriesRequest struct { @@ -7255,7 +7311,7 @@ type ListPreparedQueriesRequest struct { func (x *ListPreparedQueriesRequest) Reset() { *x = ListPreparedQueriesRequest{} - mi := &file_bucket_proto_msgTypes[108] + mi := &file_bucket_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7267,7 +7323,7 @@ func (x *ListPreparedQueriesRequest) String() string { func (*ListPreparedQueriesRequest) ProtoMessage() {} func (x *ListPreparedQueriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[108] + mi := &file_bucket_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7280,7 +7336,7 @@ func (x *ListPreparedQueriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPreparedQueriesRequest.ProtoReflect.Descriptor instead. func (*ListPreparedQueriesRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{108} + return file_bucket_proto_rawDescGZIP(), []int{109} } func (x *ListPreparedQueriesRequest) GetLedger() string { @@ -7299,7 +7355,7 @@ type ListPreparedQueriesResponse struct { func (x *ListPreparedQueriesResponse) Reset() { *x = ListPreparedQueriesResponse{} - mi := &file_bucket_proto_msgTypes[109] + mi := &file_bucket_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7311,7 +7367,7 @@ func (x *ListPreparedQueriesResponse) String() string { func (*ListPreparedQueriesResponse) ProtoMessage() {} func (x *ListPreparedQueriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[109] + mi := &file_bucket_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7324,7 +7380,7 @@ func (x *ListPreparedQueriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListPreparedQueriesResponse.ProtoReflect.Descriptor instead. func (*ListPreparedQueriesResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{109} + return file_bucket_proto_rawDescGZIP(), []int{110} } func (x *ListPreparedQueriesResponse) GetQueries() []*commonpb.PreparedQuery { @@ -7349,7 +7405,7 @@ type ExecutePreparedQueryRequest struct { func (x *ExecutePreparedQueryRequest) Reset() { *x = ExecutePreparedQueryRequest{} - mi := &file_bucket_proto_msgTypes[110] + mi := &file_bucket_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7361,7 +7417,7 @@ func (x *ExecutePreparedQueryRequest) String() string { func (*ExecutePreparedQueryRequest) ProtoMessage() {} func (x *ExecutePreparedQueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[110] + mi := &file_bucket_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7374,7 +7430,7 @@ func (x *ExecutePreparedQueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecutePreparedQueryRequest.ProtoReflect.Descriptor instead. func (*ExecutePreparedQueryRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{110} + return file_bucket_proto_rawDescGZIP(), []int{111} } func (x *ExecutePreparedQueryRequest) GetLedger() string { @@ -7439,7 +7495,7 @@ type ExecutePreparedQueryResponse struct { func (x *ExecutePreparedQueryResponse) Reset() { *x = ExecutePreparedQueryResponse{} - mi := &file_bucket_proto_msgTypes[111] + mi := &file_bucket_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7451,7 +7507,7 @@ func (x *ExecutePreparedQueryResponse) String() string { func (*ExecutePreparedQueryResponse) ProtoMessage() {} func (x *ExecutePreparedQueryResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[111] + mi := &file_bucket_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7464,7 +7520,7 @@ func (x *ExecutePreparedQueryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecutePreparedQueryResponse.ProtoReflect.Descriptor instead. func (*ExecutePreparedQueryResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{111} + return file_bucket_proto_rawDescGZIP(), []int{112} } func (x *ExecutePreparedQueryResponse) GetResult() isExecutePreparedQueryResponse_Result { @@ -7522,7 +7578,7 @@ type GetIndexStatusRequest struct { func (x *GetIndexStatusRequest) Reset() { *x = GetIndexStatusRequest{} - mi := &file_bucket_proto_msgTypes[112] + mi := &file_bucket_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7534,7 +7590,7 @@ func (x *GetIndexStatusRequest) String() string { func (*GetIndexStatusRequest) ProtoMessage() {} func (x *GetIndexStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[112] + mi := &file_bucket_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7547,7 +7603,7 @@ func (x *GetIndexStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetIndexStatusRequest.ProtoReflect.Descriptor instead. func (*GetIndexStatusRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{112} + return file_bucket_proto_rawDescGZIP(), []int{113} } func (x *GetIndexStatusRequest) GetLedger() string { @@ -7570,7 +7626,7 @@ type GetIndexStatusResponse struct { func (x *GetIndexStatusResponse) Reset() { *x = GetIndexStatusResponse{} - mi := &file_bucket_proto_msgTypes[113] + mi := &file_bucket_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7582,7 +7638,7 @@ func (x *GetIndexStatusResponse) String() string { func (*GetIndexStatusResponse) ProtoMessage() {} func (x *GetIndexStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[113] + mi := &file_bucket_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7595,7 +7651,7 @@ func (x *GetIndexStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetIndexStatusResponse.ProtoReflect.Descriptor instead. func (*GetIndexStatusResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{113} + return file_bucket_proto_rawDescGZIP(), []int{114} } func (x *GetIndexStatusResponse) GetLastIndexedSequence() uint64 { @@ -7659,7 +7715,7 @@ type IndexEntry struct { func (x *IndexEntry) Reset() { *x = IndexEntry{} - mi := &file_bucket_proto_msgTypes[114] + mi := &file_bucket_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7671,7 +7727,7 @@ func (x *IndexEntry) String() string { func (*IndexEntry) ProtoMessage() {} func (x *IndexEntry) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[114] + mi := &file_bucket_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7684,7 +7740,7 @@ func (x *IndexEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use IndexEntry.ProtoReflect.Descriptor instead. func (*IndexEntry) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{114} + return file_bucket_proto_rawDescGZIP(), []int{115} } func (x *IndexEntry) GetLedger() string { @@ -7743,7 +7799,7 @@ type ListIndexesRequest struct { func (x *ListIndexesRequest) Reset() { *x = ListIndexesRequest{} - mi := &file_bucket_proto_msgTypes[115] + mi := &file_bucket_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7755,7 +7811,7 @@ func (x *ListIndexesRequest) String() string { func (*ListIndexesRequest) ProtoMessage() {} func (x *ListIndexesRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[115] + mi := &file_bucket_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7768,7 +7824,7 @@ func (x *ListIndexesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListIndexesRequest.ProtoReflect.Descriptor instead. func (*ListIndexesRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{115} + return file_bucket_proto_rawDescGZIP(), []int{116} } func (x *ListIndexesRequest) GetScope() ListIndexesRequest_Scope { @@ -7796,7 +7852,7 @@ type GetLedgerStatsRequest struct { func (x *GetLedgerStatsRequest) Reset() { *x = GetLedgerStatsRequest{} - mi := &file_bucket_proto_msgTypes[116] + mi := &file_bucket_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7808,7 +7864,7 @@ func (x *GetLedgerStatsRequest) String() string { func (*GetLedgerStatsRequest) ProtoMessage() {} func (x *GetLedgerStatsRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[116] + mi := &file_bucket_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7821,7 +7877,7 @@ func (x *GetLedgerStatsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLedgerStatsRequest.ProtoReflect.Descriptor instead. func (*GetLedgerStatsRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{116} + return file_bucket_proto_rawDescGZIP(), []int{117} } func (x *GetLedgerStatsRequest) GetLedger() string { @@ -7857,7 +7913,7 @@ type AggregateVolumesRequest struct { func (x *AggregateVolumesRequest) Reset() { *x = AggregateVolumesRequest{} - mi := &file_bucket_proto_msgTypes[117] + mi := &file_bucket_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7869,7 +7925,7 @@ func (x *AggregateVolumesRequest) String() string { func (*AggregateVolumesRequest) ProtoMessage() {} func (x *AggregateVolumesRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[117] + mi := &file_bucket_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7882,7 +7938,7 @@ func (x *AggregateVolumesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregateVolumesRequest.ProtoReflect.Descriptor instead. func (*AggregateVolumesRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{117} + return file_bucket_proto_rawDescGZIP(), []int{118} } func (x *AggregateVolumesRequest) GetLedger() string { @@ -7944,7 +8000,7 @@ type QueryProfile struct { func (x *QueryProfile) Reset() { *x = QueryProfile{} - mi := &file_bucket_proto_msgTypes[118] + mi := &file_bucket_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7956,7 +8012,7 @@ func (x *QueryProfile) String() string { func (*QueryProfile) ProtoMessage() {} func (x *QueryProfile) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[118] + mi := &file_bucket_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7969,7 +8025,7 @@ func (x *QueryProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryProfile.ProtoReflect.Descriptor instead. func (*QueryProfile) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{118} + return file_bucket_proto_rawDescGZIP(), []int{119} } func (x *QueryProfile) GetIndexDurationUs() int64 { @@ -8046,7 +8102,7 @@ type IteratorProfile struct { func (x *IteratorProfile) Reset() { *x = IteratorProfile{} - mi := &file_bucket_proto_msgTypes[119] + mi := &file_bucket_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8058,7 +8114,7 @@ func (x *IteratorProfile) String() string { func (*IteratorProfile) ProtoMessage() {} func (x *IteratorProfile) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[119] + mi := &file_bucket_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8071,7 +8127,7 @@ func (x *IteratorProfile) ProtoReflect() protoreflect.Message { // Deprecated: Use IteratorProfile.ProtoReflect.Descriptor instead. func (*IteratorProfile) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{119} + return file_bucket_proto_rawDescGZIP(), []int{120} } func (x *IteratorProfile) GetLabel() string { @@ -8166,7 +8222,7 @@ type InspectIndexRequest struct { func (x *InspectIndexRequest) Reset() { *x = InspectIndexRequest{} - mi := &file_bucket_proto_msgTypes[120] + mi := &file_bucket_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8178,7 +8234,7 @@ func (x *InspectIndexRequest) String() string { func (*InspectIndexRequest) ProtoMessage() {} func (x *InspectIndexRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[120] + mi := &file_bucket_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8191,7 +8247,7 @@ func (x *InspectIndexRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectIndexRequest.ProtoReflect.Descriptor instead. func (*InspectIndexRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{120} + return file_bucket_proto_rawDescGZIP(), []int{121} } func (x *InspectIndexRequest) GetLedger() string { @@ -8257,7 +8313,7 @@ type InspectIndexResponse struct { func (x *InspectIndexResponse) Reset() { *x = InspectIndexResponse{} - mi := &file_bucket_proto_msgTypes[121] + mi := &file_bucket_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8269,7 +8325,7 @@ func (x *InspectIndexResponse) String() string { func (*InspectIndexResponse) ProtoMessage() {} func (x *InspectIndexResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[121] + mi := &file_bucket_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8282,7 +8338,7 @@ func (x *InspectIndexResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectIndexResponse.ProtoReflect.Descriptor instead. func (*InspectIndexResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{121} + return file_bucket_proto_rawDescGZIP(), []int{122} } func (x *InspectIndexResponse) GetResult() isInspectIndexResponse_Result { @@ -8352,7 +8408,7 @@ type InspectDistinctValues struct { func (x *InspectDistinctValues) Reset() { *x = InspectDistinctValues{} - mi := &file_bucket_proto_msgTypes[122] + mi := &file_bucket_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8364,7 +8420,7 @@ func (x *InspectDistinctValues) String() string { func (*InspectDistinctValues) ProtoMessage() {} func (x *InspectDistinctValues) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[122] + mi := &file_bucket_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8377,7 +8433,7 @@ func (x *InspectDistinctValues) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectDistinctValues.ProtoReflect.Descriptor instead. func (*InspectDistinctValues) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{122} + return file_bucket_proto_rawDescGZIP(), []int{123} } func (x *InspectDistinctValues) GetValues() []*commonpb.MetadataValue { @@ -8411,7 +8467,7 @@ type InspectFacet struct { func (x *InspectFacet) Reset() { *x = InspectFacet{} - mi := &file_bucket_proto_msgTypes[123] + mi := &file_bucket_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8423,7 +8479,7 @@ func (x *InspectFacet) String() string { func (*InspectFacet) ProtoMessage() {} func (x *InspectFacet) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[123] + mi := &file_bucket_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8436,7 +8492,7 @@ func (x *InspectFacet) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectFacet.ProtoReflect.Descriptor instead. func (*InspectFacet) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{123} + return file_bucket_proto_rawDescGZIP(), []int{124} } func (x *InspectFacet) GetValue() *commonpb.MetadataValue { @@ -8464,7 +8520,7 @@ type InspectFacets struct { func (x *InspectFacets) Reset() { *x = InspectFacets{} - mi := &file_bucket_proto_msgTypes[124] + mi := &file_bucket_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8476,7 +8532,7 @@ func (x *InspectFacets) String() string { func (*InspectFacets) ProtoMessage() {} func (x *InspectFacets) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[124] + mi := &file_bucket_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8489,7 +8545,7 @@ func (x *InspectFacets) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectFacets.ProtoReflect.Descriptor instead. func (*InspectFacets) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{124} + return file_bucket_proto_rawDescGZIP(), []int{125} } func (x *InspectFacets) GetFacets() []*InspectFacet { @@ -8526,7 +8582,7 @@ type InspectSummary struct { func (x *InspectSummary) Reset() { *x = InspectSummary{} - mi := &file_bucket_proto_msgTypes[125] + mi := &file_bucket_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8538,7 +8594,7 @@ func (x *InspectSummary) String() string { func (*InspectSummary) ProtoMessage() {} func (x *InspectSummary) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[125] + mi := &file_bucket_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8551,7 +8607,7 @@ func (x *InspectSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use InspectSummary.ProtoReflect.Descriptor instead. func (*InspectSummary) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{125} + return file_bucket_proto_rawDescGZIP(), []int{126} } func (x *InspectSummary) GetCardinality() uint64 { @@ -8597,7 +8653,7 @@ type BarrierRequest struct { func (x *BarrierRequest) Reset() { *x = BarrierRequest{} - mi := &file_bucket_proto_msgTypes[126] + mi := &file_bucket_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8609,7 +8665,7 @@ func (x *BarrierRequest) String() string { func (*BarrierRequest) ProtoMessage() {} func (x *BarrierRequest) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[126] + mi := &file_bucket_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8622,7 +8678,7 @@ func (x *BarrierRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BarrierRequest.ProtoReflect.Descriptor instead. func (*BarrierRequest) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{126} + return file_bucket_proto_rawDescGZIP(), []int{127} } type BarrierResponse struct { @@ -8638,7 +8694,7 @@ type BarrierResponse struct { func (x *BarrierResponse) Reset() { *x = BarrierResponse{} - mi := &file_bucket_proto_msgTypes[127] + mi := &file_bucket_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8650,7 +8706,7 @@ func (x *BarrierResponse) String() string { func (*BarrierResponse) ProtoMessage() {} func (x *BarrierResponse) ProtoReflect() protoreflect.Message { - mi := &file_bucket_proto_msgTypes[127] + mi := &file_bucket_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8663,7 +8719,7 @@ func (x *BarrierResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BarrierResponse.ProtoReflect.Descriptor instead. func (*BarrierResponse) Descriptor() ([]byte, []int) { - return file_bucket_proto_rawDescGZIP(), []int{127} + return file_bucket_proto_rawDescGZIP(), []int{128} } func (x *BarrierResponse) GetCommitIndex() uint64 { @@ -8844,7 +8900,10 @@ const file_bucket_proto_rawDesc = "" + "\x04read\x18\x04 \x01(\v2\x13.common.ReadOptionsR\x04readR\rcheckpoint_id\"^\n" + "\x15ListNumscriptsRequest\x12\x16\n" + "\x06ledger\x18\x01 \x01(\tR\x06ledger\x12-\n" + - "\aoptions\x18\x02 \x01(\v2\x13.common.ListOptionsR\aoptions\"\xaf\x01\n" + + "\aoptions\x18\x02 \x01(\v2\x13.common.ListOptionsR\aoptions\"E\n" + + "\x17GetTemplateUsageRequest\x12\x16\n" + + "\x06ledger\x18\x01 \x01(\tR\x06ledger\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"\xaf\x01\n" + "\x0fScriptReference\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\aversion\x18\x02 \x01(\tR\aversion\x125\n" + @@ -9305,7 +9364,7 @@ const file_bucket_proto_rawDesc = "" + "\x10InspectIndexMode\x12&\n" + "\"INSPECT_INDEX_MODE_DISTINCT_VALUES\x10\x00\x12\x1d\n" + "\x19INSPECT_INDEX_MODE_FACETS\x10\x01\x12\x1e\n" + - "\x1aINSPECT_INDEX_MODE_SUMMARY\x10\x022\xf4\x14\n" + + "\x1aINSPECT_INDEX_MODE_SUMMARY\x10\x022\xc0\x15\n" + "\rBucketService\x12?\n" + "\vListLedgers\x12\x1a.ledger.ListLedgersRequest\x1a\x12.common.LedgerInfo0\x01\x129\n" + "\tGetLedger\x12\x18.ledger.GetLedgerRequest\x1a\x12.common.LedgerInfo\x128\n" + @@ -9341,7 +9400,8 @@ const file_bucket_proto_rawDesc = "" + "\x0eGetLedgerStats\x12\x1d.ledger.GetLedgerStatsRequest\x1a\x13.common.LedgerStats\x12L\n" + "\x10AggregateVolumes\x12\x1f.ledger.AggregateVolumesRequest\x1a\x17.common.AggregateResult\x12B\n" + "\fGetNumscript\x12\x1b.ledger.GetNumscriptRequest\x1a\x15.common.NumscriptInfo\x12H\n" + - "\x0eListNumscripts\x12\x1d.ledger.ListNumscriptsRequest\x1a\x15.common.NumscriptInfo0\x01\x12I\n" + + "\x0eListNumscripts\x12\x1d.ledger.ListNumscriptsRequest\x1a\x15.common.NumscriptInfo0\x01\x12J\n" + + "\x10GetTemplateUsage\x12\x1f.ledger.GetTemplateUsageRequest\x1a\x15.common.TemplateUsage\x12I\n" + "\fInspectIndex\x12\x1b.ledger.InspectIndexRequest\x1a\x1c.ledger.InspectIndexResponse\x12:\n" + "\aBarrier\x12\x16.ledger.BarrierRequest\x1a\x17.ledger.BarrierResponseB:Z8github.com/formancehq/ledger/v3/internal/proto/servicepbb\x06proto3" @@ -9358,7 +9418,7 @@ func file_bucket_proto_rawDescGZIP() []byte { } var file_bucket_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_bucket_proto_msgTypes = make([]protoimpl.MessageInfo, 138) +var file_bucket_proto_msgTypes = make([]protoimpl.MessageInfo, 139) var file_bucket_proto_goTypes = []any{ (CheckStoreErrorType)(0), // 0: ledger.CheckStoreErrorType (PatternSegmentType)(0), // 1: ledger.PatternSegmentType @@ -9406,158 +9466,160 @@ var file_bucket_proto_goTypes = []any{ (*DeleteNumscriptRequest)(nil), // 43: ledger.DeleteNumscriptRequest (*GetNumscriptRequest)(nil), // 44: ledger.GetNumscriptRequest (*ListNumscriptsRequest)(nil), // 45: ledger.ListNumscriptsRequest - (*ScriptReference)(nil), // 46: ledger.ScriptReference - (*SetQueryCheckpointScheduleRequest)(nil), // 47: ledger.SetQueryCheckpointScheduleRequest - (*DeleteQueryCheckpointScheduleRequest)(nil), // 48: ledger.DeleteQueryCheckpointScheduleRequest - (*GetChapterScheduleRequest)(nil), // 49: ledger.GetChapterScheduleRequest - (*GetChapterScheduleResponse)(nil), // 50: ledger.GetChapterScheduleResponse - (*DiscoveryRequest)(nil), // 51: ledger.DiscoveryRequest - (*ServerInfo)(nil), // 52: ledger.ServerInfo - (*DiscoveryResponse)(nil), // 53: ledger.DiscoveryResponse - (*ResponseSigningInfo)(nil), // 54: ledger.ResponseSigningInfo - (*CreateTransactionPayload)(nil), // 55: ledger.CreateTransactionPayload - (*RevertTransactionPayload)(nil), // 56: ledger.RevertTransactionPayload - (*LedgerAction)(nil), // 57: ledger.LedgerAction - (*LedgerApplyRequest)(nil), // 58: ledger.LedgerApplyRequest - (*AddAccountTypeRequest)(nil), // 59: ledger.AddAccountTypeRequest - (*RemoveAccountTypeRequest)(nil), // 60: ledger.RemoveAccountTypeRequest - (*SetDefaultEnforcementModeRequest)(nil), // 61: ledger.SetDefaultEnforcementModeRequest - (*SetDefaultEnforcementModeLedgerRequest)(nil), // 62: ledger.SetDefaultEnforcementModeLedgerRequest - (*AddAccountTypeLedgerRequest)(nil), // 63: ledger.AddAccountTypeLedgerRequest - (*RemoveAccountTypeLedgerRequest)(nil), // 64: ledger.RemoveAccountTypeLedgerRequest - (*GetPrimaryMetricsRequest)(nil), // 65: ledger.GetPrimaryMetricsRequest - (*GetPrimaryMetricsResponse)(nil), // 66: ledger.GetPrimaryMetricsResponse - (*GetSecondaryMetricsRequest)(nil), // 67: ledger.GetSecondaryMetricsRequest - (*GetSecondaryMetricsResponse)(nil), // 68: ledger.GetSecondaryMetricsResponse - (*PebbleMetrics)(nil), // 69: ledger.PebbleMetrics - (*BlockCacheMetrics)(nil), // 70: ledger.BlockCacheMetrics - (*CompactMetrics)(nil), // 71: ledger.CompactMetrics - (*FlushMetrics)(nil), // 72: ledger.FlushMetrics - (*MemTableMetrics)(nil), // 73: ledger.MemTableMetrics - (*SnapshotsMetrics)(nil), // 74: ledger.SnapshotsMetrics - (*TableMetrics)(nil), // 75: ledger.TableMetrics - (*TableCacheMetrics)(nil), // 76: ledger.TableCacheMetrics - (*WALMetrics)(nil), // 77: ledger.WALMetrics - (*KeysMetrics)(nil), // 78: ledger.KeysMetrics - (*LevelMetrics)(nil), // 79: ledger.LevelMetrics - (*CheckStoreRequest)(nil), // 80: ledger.CheckStoreRequest - (*CheckStoreEvent)(nil), // 81: ledger.CheckStoreEvent - (*CheckStoreError)(nil), // 82: ledger.CheckStoreError - (*CheckStoreProgress)(nil), // 83: ledger.CheckStoreProgress - (*ListAuditEntriesRequest)(nil), // 84: ledger.ListAuditEntriesRequest - (*GetAuditEntryRequest)(nil), // 85: ledger.GetAuditEntryRequest - (*ListLogsRequest)(nil), // 86: ledger.ListLogsRequest - (*GetLogRequest)(nil), // 87: ledger.GetLogRequest - (*GetEventsSinksRequest)(nil), // 88: ledger.GetEventsSinksRequest - (*GetEventsSinksResponse)(nil), // 89: ledger.GetEventsSinksResponse - (*GetMetadataSchemaStatusRequest)(nil), // 90: ledger.GetMetadataSchemaStatusRequest - (*GetMetadataSchemaStatusResponse)(nil), // 91: ledger.GetMetadataSchemaStatusResponse - (*MetadataFieldStatus)(nil), // 92: ledger.MetadataFieldStatus - (*AnalyzeAccountsRequest)(nil), // 93: ledger.AnalyzeAccountsRequest - (*AnalyzeAccountsResponse)(nil), // 94: ledger.AnalyzeAccountsResponse - (*AnalyzeProgress)(nil), // 95: ledger.AnalyzeProgress - (*AnalyzeAccountsEvent)(nil), // 96: ledger.AnalyzeAccountsEvent - (*AnalyzeTransactionsEvent)(nil), // 97: ledger.AnalyzeTransactionsEvent - (*AccountPattern)(nil), // 98: ledger.AccountPattern - (*PatternSegment)(nil), // 99: ledger.PatternSegment - (*AnalyzeTransactionsRequest)(nil), // 100: ledger.AnalyzeTransactionsRequest - (*AnalyzeTransactionsResponse)(nil), // 101: ledger.AnalyzeTransactionsResponse - (*FlowPattern)(nil), // 102: ledger.FlowPattern - (*NormalizedPosting)(nil), // 103: ledger.NormalizedPosting - (*TemporalStats)(nil), // 104: ledger.TemporalStats - (*HourBucket)(nil), // 105: ledger.HourBucket - (*AssetVolumeStats)(nil), // 106: ledger.AssetVolumeStats - (*CreatePreparedQueryRequest)(nil), // 107: ledger.CreatePreparedQueryRequest - (*CreatePreparedQueryResponse)(nil), // 108: ledger.CreatePreparedQueryResponse - (*UpdatePreparedQueryRequest)(nil), // 109: ledger.UpdatePreparedQueryRequest - (*UpdatePreparedQueryResponse)(nil), // 110: ledger.UpdatePreparedQueryResponse - (*DeletePreparedQueryRequest)(nil), // 111: ledger.DeletePreparedQueryRequest - (*DeletePreparedQueryResponse)(nil), // 112: ledger.DeletePreparedQueryResponse - (*ListPreparedQueriesRequest)(nil), // 113: ledger.ListPreparedQueriesRequest - (*ListPreparedQueriesResponse)(nil), // 114: ledger.ListPreparedQueriesResponse - (*ExecutePreparedQueryRequest)(nil), // 115: ledger.ExecutePreparedQueryRequest - (*ExecutePreparedQueryResponse)(nil), // 116: ledger.ExecutePreparedQueryResponse - (*GetIndexStatusRequest)(nil), // 117: ledger.GetIndexStatusRequest - (*GetIndexStatusResponse)(nil), // 118: ledger.GetIndexStatusResponse - (*IndexEntry)(nil), // 119: ledger.IndexEntry - (*ListIndexesRequest)(nil), // 120: ledger.ListIndexesRequest - (*GetLedgerStatsRequest)(nil), // 121: ledger.GetLedgerStatsRequest - (*AggregateVolumesRequest)(nil), // 122: ledger.AggregateVolumesRequest - (*QueryProfile)(nil), // 123: ledger.QueryProfile - (*IteratorProfile)(nil), // 124: ledger.IteratorProfile - (*InspectIndexRequest)(nil), // 125: ledger.InspectIndexRequest - (*InspectIndexResponse)(nil), // 126: ledger.InspectIndexResponse - (*InspectDistinctValues)(nil), // 127: ledger.InspectDistinctValues - (*InspectFacet)(nil), // 128: ledger.InspectFacet - (*InspectFacets)(nil), // 129: ledger.InspectFacets - (*InspectSummary)(nil), // 130: ledger.InspectSummary - (*BarrierRequest)(nil), // 131: ledger.BarrierRequest - (*BarrierResponse)(nil), // 132: ledger.BarrierResponse - nil, // 133: ledger.CreateLedgerRequest.AccountTypesEntry - nil, // 134: ledger.SaveLedgerMetadataRequest.MetadataEntry - nil, // 135: ledger.ScriptReference.VarsEntry - nil, // 136: ledger.CreateTransactionPayload.MetadataEntry - nil, // 137: ledger.CreateTransactionPayload.AccountMetadataEntry - nil, // 138: ledger.RevertTransactionPayload.MetadataEntry - nil, // 139: ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry - nil, // 140: ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry - nil, // 141: ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry - nil, // 142: ledger.ExecutePreparedQueryRequest.ParametersEntry - (*commonpb.Transaction)(nil), // 143: common.Transaction - (*commonpb.ListOptions)(nil), // 144: common.ListOptions - (*commonpb.SetMetadataFieldTypeCommand)(nil), // 145: common.SetMetadataFieldTypeCommand - (commonpb.LedgerMode)(0), // 146: common.LedgerMode - (*commonpb.MirrorSourceConfig)(nil), // 147: common.MirrorSourceConfig - (commonpb.ChartEnforcementMode)(0), // 148: common.ChartEnforcementMode - (*commonpb.ReadOptions)(nil), // 149: common.ReadOptions - (*signaturepb.SignedApplyBatch)(nil), // 150: signature.SignedApplyBatch - (*commonpb.CallerSnapshot)(nil), // 151: common.CallerSnapshot - (*commonpb.Log)(nil), // 152: common.Log - (*commonpb.SinkConfig)(nil), // 153: common.SinkConfig - (commonpb.TargetType)(0), // 154: common.TargetType - (commonpb.MetadataType)(0), // 155: common.MetadataType - (*commonpb.IndexID)(nil), // 156: common.IndexID - (*commonpb.Posting)(nil), // 157: common.Posting - (*commonpb.Script)(nil), // 158: common.Script - (*commonpb.Timestamp)(nil), // 159: common.Timestamp - (*commonpb.SaveMetadataCommand)(nil), // 160: common.SaveMetadataCommand - (*commonpb.DeleteMetadataCommand)(nil), // 161: common.DeleteMetadataCommand - (*commonpb.AccountType)(nil), // 162: common.AccountType - (*commonpb.SinkStatus)(nil), // 163: common.SinkStatus - (*commonpb.PreparedQuery)(nil), // 164: common.PreparedQuery - (*commonpb.QueryFilter)(nil), // 165: common.QueryFilter - (commonpb.QueryMode)(0), // 166: common.QueryMode - (*commonpb.PreparedQueryCursor)(nil), // 167: common.PreparedQueryCursor - (*commonpb.AggregateResult)(nil), // 168: common.AggregateResult - (*commonpb.Index)(nil), // 169: common.Index - (*commonpb.MetadataValue)(nil), // 170: common.MetadataValue - (*commonpb.MetadataMap)(nil), // 171: common.MetadataMap - (*commonpb.ParameterValue)(nil), // 172: common.ParameterValue - (*commonpb.LedgerInfo)(nil), // 173: common.LedgerInfo - (*commonpb.Account)(nil), // 174: common.Account - (*auditpb.AuditEntry)(nil), // 175: audit.AuditEntry - (*commonpb.Chapter)(nil), // 176: common.Chapter - (*commonpb.SigningKey)(nil), // 177: common.SigningKey - (*commonpb.LedgerStats)(nil), // 178: common.LedgerStats - (*commonpb.NumscriptInfo)(nil), // 179: common.NumscriptInfo + (*GetTemplateUsageRequest)(nil), // 46: ledger.GetTemplateUsageRequest + (*ScriptReference)(nil), // 47: ledger.ScriptReference + (*SetQueryCheckpointScheduleRequest)(nil), // 48: ledger.SetQueryCheckpointScheduleRequest + (*DeleteQueryCheckpointScheduleRequest)(nil), // 49: ledger.DeleteQueryCheckpointScheduleRequest + (*GetChapterScheduleRequest)(nil), // 50: ledger.GetChapterScheduleRequest + (*GetChapterScheduleResponse)(nil), // 51: ledger.GetChapterScheduleResponse + (*DiscoveryRequest)(nil), // 52: ledger.DiscoveryRequest + (*ServerInfo)(nil), // 53: ledger.ServerInfo + (*DiscoveryResponse)(nil), // 54: ledger.DiscoveryResponse + (*ResponseSigningInfo)(nil), // 55: ledger.ResponseSigningInfo + (*CreateTransactionPayload)(nil), // 56: ledger.CreateTransactionPayload + (*RevertTransactionPayload)(nil), // 57: ledger.RevertTransactionPayload + (*LedgerAction)(nil), // 58: ledger.LedgerAction + (*LedgerApplyRequest)(nil), // 59: ledger.LedgerApplyRequest + (*AddAccountTypeRequest)(nil), // 60: ledger.AddAccountTypeRequest + (*RemoveAccountTypeRequest)(nil), // 61: ledger.RemoveAccountTypeRequest + (*SetDefaultEnforcementModeRequest)(nil), // 62: ledger.SetDefaultEnforcementModeRequest + (*SetDefaultEnforcementModeLedgerRequest)(nil), // 63: ledger.SetDefaultEnforcementModeLedgerRequest + (*AddAccountTypeLedgerRequest)(nil), // 64: ledger.AddAccountTypeLedgerRequest + (*RemoveAccountTypeLedgerRequest)(nil), // 65: ledger.RemoveAccountTypeLedgerRequest + (*GetPrimaryMetricsRequest)(nil), // 66: ledger.GetPrimaryMetricsRequest + (*GetPrimaryMetricsResponse)(nil), // 67: ledger.GetPrimaryMetricsResponse + (*GetSecondaryMetricsRequest)(nil), // 68: ledger.GetSecondaryMetricsRequest + (*GetSecondaryMetricsResponse)(nil), // 69: ledger.GetSecondaryMetricsResponse + (*PebbleMetrics)(nil), // 70: ledger.PebbleMetrics + (*BlockCacheMetrics)(nil), // 71: ledger.BlockCacheMetrics + (*CompactMetrics)(nil), // 72: ledger.CompactMetrics + (*FlushMetrics)(nil), // 73: ledger.FlushMetrics + (*MemTableMetrics)(nil), // 74: ledger.MemTableMetrics + (*SnapshotsMetrics)(nil), // 75: ledger.SnapshotsMetrics + (*TableMetrics)(nil), // 76: ledger.TableMetrics + (*TableCacheMetrics)(nil), // 77: ledger.TableCacheMetrics + (*WALMetrics)(nil), // 78: ledger.WALMetrics + (*KeysMetrics)(nil), // 79: ledger.KeysMetrics + (*LevelMetrics)(nil), // 80: ledger.LevelMetrics + (*CheckStoreRequest)(nil), // 81: ledger.CheckStoreRequest + (*CheckStoreEvent)(nil), // 82: ledger.CheckStoreEvent + (*CheckStoreError)(nil), // 83: ledger.CheckStoreError + (*CheckStoreProgress)(nil), // 84: ledger.CheckStoreProgress + (*ListAuditEntriesRequest)(nil), // 85: ledger.ListAuditEntriesRequest + (*GetAuditEntryRequest)(nil), // 86: ledger.GetAuditEntryRequest + (*ListLogsRequest)(nil), // 87: ledger.ListLogsRequest + (*GetLogRequest)(nil), // 88: ledger.GetLogRequest + (*GetEventsSinksRequest)(nil), // 89: ledger.GetEventsSinksRequest + (*GetEventsSinksResponse)(nil), // 90: ledger.GetEventsSinksResponse + (*GetMetadataSchemaStatusRequest)(nil), // 91: ledger.GetMetadataSchemaStatusRequest + (*GetMetadataSchemaStatusResponse)(nil), // 92: ledger.GetMetadataSchemaStatusResponse + (*MetadataFieldStatus)(nil), // 93: ledger.MetadataFieldStatus + (*AnalyzeAccountsRequest)(nil), // 94: ledger.AnalyzeAccountsRequest + (*AnalyzeAccountsResponse)(nil), // 95: ledger.AnalyzeAccountsResponse + (*AnalyzeProgress)(nil), // 96: ledger.AnalyzeProgress + (*AnalyzeAccountsEvent)(nil), // 97: ledger.AnalyzeAccountsEvent + (*AnalyzeTransactionsEvent)(nil), // 98: ledger.AnalyzeTransactionsEvent + (*AccountPattern)(nil), // 99: ledger.AccountPattern + (*PatternSegment)(nil), // 100: ledger.PatternSegment + (*AnalyzeTransactionsRequest)(nil), // 101: ledger.AnalyzeTransactionsRequest + (*AnalyzeTransactionsResponse)(nil), // 102: ledger.AnalyzeTransactionsResponse + (*FlowPattern)(nil), // 103: ledger.FlowPattern + (*NormalizedPosting)(nil), // 104: ledger.NormalizedPosting + (*TemporalStats)(nil), // 105: ledger.TemporalStats + (*HourBucket)(nil), // 106: ledger.HourBucket + (*AssetVolumeStats)(nil), // 107: ledger.AssetVolumeStats + (*CreatePreparedQueryRequest)(nil), // 108: ledger.CreatePreparedQueryRequest + (*CreatePreparedQueryResponse)(nil), // 109: ledger.CreatePreparedQueryResponse + (*UpdatePreparedQueryRequest)(nil), // 110: ledger.UpdatePreparedQueryRequest + (*UpdatePreparedQueryResponse)(nil), // 111: ledger.UpdatePreparedQueryResponse + (*DeletePreparedQueryRequest)(nil), // 112: ledger.DeletePreparedQueryRequest + (*DeletePreparedQueryResponse)(nil), // 113: ledger.DeletePreparedQueryResponse + (*ListPreparedQueriesRequest)(nil), // 114: ledger.ListPreparedQueriesRequest + (*ListPreparedQueriesResponse)(nil), // 115: ledger.ListPreparedQueriesResponse + (*ExecutePreparedQueryRequest)(nil), // 116: ledger.ExecutePreparedQueryRequest + (*ExecutePreparedQueryResponse)(nil), // 117: ledger.ExecutePreparedQueryResponse + (*GetIndexStatusRequest)(nil), // 118: ledger.GetIndexStatusRequest + (*GetIndexStatusResponse)(nil), // 119: ledger.GetIndexStatusResponse + (*IndexEntry)(nil), // 120: ledger.IndexEntry + (*ListIndexesRequest)(nil), // 121: ledger.ListIndexesRequest + (*GetLedgerStatsRequest)(nil), // 122: ledger.GetLedgerStatsRequest + (*AggregateVolumesRequest)(nil), // 123: ledger.AggregateVolumesRequest + (*QueryProfile)(nil), // 124: ledger.QueryProfile + (*IteratorProfile)(nil), // 125: ledger.IteratorProfile + (*InspectIndexRequest)(nil), // 126: ledger.InspectIndexRequest + (*InspectIndexResponse)(nil), // 127: ledger.InspectIndexResponse + (*InspectDistinctValues)(nil), // 128: ledger.InspectDistinctValues + (*InspectFacet)(nil), // 129: ledger.InspectFacet + (*InspectFacets)(nil), // 130: ledger.InspectFacets + (*InspectSummary)(nil), // 131: ledger.InspectSummary + (*BarrierRequest)(nil), // 132: ledger.BarrierRequest + (*BarrierResponse)(nil), // 133: ledger.BarrierResponse + nil, // 134: ledger.CreateLedgerRequest.AccountTypesEntry + nil, // 135: ledger.SaveLedgerMetadataRequest.MetadataEntry + nil, // 136: ledger.ScriptReference.VarsEntry + nil, // 137: ledger.CreateTransactionPayload.MetadataEntry + nil, // 138: ledger.CreateTransactionPayload.AccountMetadataEntry + nil, // 139: ledger.RevertTransactionPayload.MetadataEntry + nil, // 140: ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry + nil, // 141: ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry + nil, // 142: ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry + nil, // 143: ledger.ExecutePreparedQueryRequest.ParametersEntry + (*commonpb.Transaction)(nil), // 144: common.Transaction + (*commonpb.ListOptions)(nil), // 145: common.ListOptions + (*commonpb.SetMetadataFieldTypeCommand)(nil), // 146: common.SetMetadataFieldTypeCommand + (commonpb.LedgerMode)(0), // 147: common.LedgerMode + (*commonpb.MirrorSourceConfig)(nil), // 148: common.MirrorSourceConfig + (commonpb.ChartEnforcementMode)(0), // 149: common.ChartEnforcementMode + (*commonpb.ReadOptions)(nil), // 150: common.ReadOptions + (*signaturepb.SignedApplyBatch)(nil), // 151: signature.SignedApplyBatch + (*commonpb.CallerSnapshot)(nil), // 152: common.CallerSnapshot + (*commonpb.Log)(nil), // 153: common.Log + (*commonpb.SinkConfig)(nil), // 154: common.SinkConfig + (commonpb.TargetType)(0), // 155: common.TargetType + (commonpb.MetadataType)(0), // 156: common.MetadataType + (*commonpb.IndexID)(nil), // 157: common.IndexID + (*commonpb.Posting)(nil), // 158: common.Posting + (*commonpb.Script)(nil), // 159: common.Script + (*commonpb.Timestamp)(nil), // 160: common.Timestamp + (*commonpb.SaveMetadataCommand)(nil), // 161: common.SaveMetadataCommand + (*commonpb.DeleteMetadataCommand)(nil), // 162: common.DeleteMetadataCommand + (*commonpb.AccountType)(nil), // 163: common.AccountType + (*commonpb.SinkStatus)(nil), // 164: common.SinkStatus + (*commonpb.PreparedQuery)(nil), // 165: common.PreparedQuery + (*commonpb.QueryFilter)(nil), // 166: common.QueryFilter + (commonpb.QueryMode)(0), // 167: common.QueryMode + (*commonpb.PreparedQueryCursor)(nil), // 168: common.PreparedQueryCursor + (*commonpb.AggregateResult)(nil), // 169: common.AggregateResult + (*commonpb.Index)(nil), // 170: common.Index + (*commonpb.MetadataValue)(nil), // 171: common.MetadataValue + (*commonpb.MetadataMap)(nil), // 172: common.MetadataMap + (*commonpb.ParameterValue)(nil), // 173: common.ParameterValue + (*commonpb.LedgerInfo)(nil), // 174: common.LedgerInfo + (*commonpb.Account)(nil), // 175: common.Account + (*auditpb.AuditEntry)(nil), // 176: audit.AuditEntry + (*commonpb.Chapter)(nil), // 177: common.Chapter + (*commonpb.SigningKey)(nil), // 178: common.SigningKey + (*commonpb.LedgerStats)(nil), // 179: common.LedgerStats + (*commonpb.NumscriptInfo)(nil), // 180: common.NumscriptInfo + (*commonpb.TemplateUsage)(nil), // 181: common.TemplateUsage } var file_bucket_proto_depIdxs = []int32{ - 143, // 0: ledger.GetTransactionResponse.transaction:type_name -> common.Transaction - 144, // 1: ledger.ListTransactionsRequest.options:type_name -> common.ListOptions - 144, // 2: ledger.ListAccountsRequest.options:type_name -> common.ListOptions - 145, // 3: ledger.CreateLedgerRequest.initial_schema:type_name -> common.SetMetadataFieldTypeCommand - 146, // 4: ledger.CreateLedgerRequest.mode:type_name -> common.LedgerMode - 147, // 5: ledger.CreateLedgerRequest.mirror_source:type_name -> common.MirrorSourceConfig - 133, // 6: ledger.CreateLedgerRequest.account_types:type_name -> ledger.CreateLedgerRequest.AccountTypesEntry - 148, // 7: ledger.CreateLedgerRequest.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 144, // 8: ledger.ListLedgersRequest.options:type_name -> common.ListOptions - 149, // 9: ledger.GetLedgerRequest.read:type_name -> common.ReadOptions + 144, // 0: ledger.GetTransactionResponse.transaction:type_name -> common.Transaction + 145, // 1: ledger.ListTransactionsRequest.options:type_name -> common.ListOptions + 145, // 2: ledger.ListAccountsRequest.options:type_name -> common.ListOptions + 146, // 3: ledger.CreateLedgerRequest.initial_schema:type_name -> common.SetMetadataFieldTypeCommand + 147, // 4: ledger.CreateLedgerRequest.mode:type_name -> common.LedgerMode + 148, // 5: ledger.CreateLedgerRequest.mirror_source:type_name -> common.MirrorSourceConfig + 134, // 6: ledger.CreateLedgerRequest.account_types:type_name -> ledger.CreateLedgerRequest.AccountTypesEntry + 149, // 7: ledger.CreateLedgerRequest.default_enforcement_mode:type_name -> common.ChartEnforcementMode + 145, // 8: ledger.ListLedgersRequest.options:type_name -> common.ListOptions + 150, // 9: ledger.GetLedgerRequest.read:type_name -> common.ReadOptions 16, // 10: ledger.ApplyRequest.unsigned:type_name -> ledger.ApplyBatch - 150, // 11: ledger.ApplyRequest.signed:type_name -> signature.SignedApplyBatch - 151, // 12: ledger.ApplyRequest.forwarded_caller_snapshot:type_name -> common.CallerSnapshot + 151, // 11: ledger.ApplyRequest.signed:type_name -> signature.SignedApplyBatch + 152, // 12: ledger.ApplyRequest.forwarded_caller_snapshot:type_name -> common.CallerSnapshot 18, // 13: ledger.ApplyBatch.requests:type_name -> ledger.Request - 152, // 14: ledger.ApplyResponse.logs:type_name -> common.Log - 58, // 15: ledger.Request.apply:type_name -> ledger.LedgerApplyRequest + 153, // 14: ledger.ApplyResponse.logs:type_name -> common.Log + 59, // 15: ledger.Request.apply:type_name -> ledger.LedgerApplyRequest 10, // 16: ledger.Request.create_ledger:type_name -> ledger.CreateLedgerRequest 11, // 17: ledger.Request.delete_ledger:type_name -> ledger.DeleteLedgerRequest 26, // 18: ledger.Request.register_signing_key:type_name -> ledger.RegisterSigningKeyRequest @@ -9575,125 +9637,125 @@ var file_bucket_proto_depIdxs = []int32{ 38, // 30: ledger.Request.set_metadata_field_type:type_name -> ledger.SetMetadataFieldTypeRequest 39, // 31: ledger.Request.remove_metadata_field_type:type_name -> ledger.RemoveMetadataFieldTypeRequest 21, // 32: ledger.Request.promote_ledger:type_name -> ledger.PromoteLedgerRequest - 107, // 33: ledger.Request.create_prepared_query:type_name -> ledger.CreatePreparedQueryRequest - 109, // 34: ledger.Request.update_prepared_query:type_name -> ledger.UpdatePreparedQueryRequest - 111, // 35: ledger.Request.delete_prepared_query:type_name -> ledger.DeletePreparedQueryRequest + 108, // 33: ledger.Request.create_prepared_query:type_name -> ledger.CreatePreparedQueryRequest + 110, // 34: ledger.Request.update_prepared_query:type_name -> ledger.UpdatePreparedQueryRequest + 112, // 35: ledger.Request.delete_prepared_query:type_name -> ledger.DeletePreparedQueryRequest 40, // 36: ledger.Request.create_index:type_name -> ledger.CreateIndexRequest 41, // 37: ledger.Request.drop_index:type_name -> ledger.DropIndexRequest 42, // 38: ledger.Request.save_numscript:type_name -> ledger.SaveNumscriptRequest 43, // 39: ledger.Request.delete_numscript:type_name -> ledger.DeleteNumscriptRequest - 63, // 40: ledger.Request.add_account_type:type_name -> ledger.AddAccountTypeLedgerRequest - 64, // 41: ledger.Request.remove_account_type:type_name -> ledger.RemoveAccountTypeLedgerRequest - 62, // 42: ledger.Request.set_default_enforcement_mode:type_name -> ledger.SetDefaultEnforcementModeLedgerRequest + 64, // 40: ledger.Request.add_account_type:type_name -> ledger.AddAccountTypeLedgerRequest + 65, // 41: ledger.Request.remove_account_type:type_name -> ledger.RemoveAccountTypeLedgerRequest + 63, // 42: ledger.Request.set_default_enforcement_mode:type_name -> ledger.SetDefaultEnforcementModeLedgerRequest 19, // 43: ledger.Request.create_query_checkpoint:type_name -> ledger.CreateQueryCheckpointRequest 20, // 44: ledger.Request.delete_query_checkpoint:type_name -> ledger.DeleteQueryCheckpointRequest - 47, // 45: ledger.Request.set_query_checkpoint_schedule:type_name -> ledger.SetQueryCheckpointScheduleRequest - 48, // 46: ledger.Request.delete_query_checkpoint_schedule:type_name -> ledger.DeleteQueryCheckpointScheduleRequest + 48, // 45: ledger.Request.set_query_checkpoint_schedule:type_name -> ledger.SetQueryCheckpointScheduleRequest + 49, // 46: ledger.Request.delete_query_checkpoint_schedule:type_name -> ledger.DeleteQueryCheckpointScheduleRequest 22, // 47: ledger.Request.save_ledger_metadata:type_name -> ledger.SaveLedgerMetadataRequest 23, // 48: ledger.Request.delete_ledger_metadata:type_name -> ledger.DeleteLedgerMetadataRequest - 134, // 49: ledger.SaveLedgerMetadataRequest.metadata:type_name -> ledger.SaveLedgerMetadataRequest.MetadataEntry - 153, // 50: ledger.AddEventsSinkRequest.config:type_name -> common.SinkConfig - 144, // 51: ledger.ListSigningKeysRequest.options:type_name -> common.ListOptions - 144, // 52: ledger.ListChaptersRequest.options:type_name -> common.ListOptions - 154, // 53: ledger.SetMetadataFieldTypeRequest.target_type:type_name -> common.TargetType - 155, // 54: ledger.SetMetadataFieldTypeRequest.type:type_name -> common.MetadataType - 154, // 55: ledger.RemoveMetadataFieldTypeRequest.target_type:type_name -> common.TargetType - 156, // 56: ledger.CreateIndexRequest.id:type_name -> common.IndexID - 156, // 57: ledger.DropIndexRequest.id:type_name -> common.IndexID - 149, // 58: ledger.GetNumscriptRequest.read:type_name -> common.ReadOptions - 144, // 59: ledger.ListNumscriptsRequest.options:type_name -> common.ListOptions - 135, // 60: ledger.ScriptReference.vars:type_name -> ledger.ScriptReference.VarsEntry - 54, // 61: ledger.DiscoveryResponse.response_signing:type_name -> ledger.ResponseSigningInfo - 52, // 62: ledger.DiscoveryResponse.server_info:type_name -> ledger.ServerInfo - 157, // 63: ledger.CreateTransactionPayload.postings:type_name -> common.Posting - 158, // 64: ledger.CreateTransactionPayload.script:type_name -> common.Script - 159, // 65: ledger.CreateTransactionPayload.timestamp:type_name -> common.Timestamp - 136, // 66: ledger.CreateTransactionPayload.metadata:type_name -> ledger.CreateTransactionPayload.MetadataEntry - 137, // 67: ledger.CreateTransactionPayload.account_metadata:type_name -> ledger.CreateTransactionPayload.AccountMetadataEntry - 46, // 68: ledger.CreateTransactionPayload.script_reference:type_name -> ledger.ScriptReference - 138, // 69: ledger.RevertTransactionPayload.metadata:type_name -> ledger.RevertTransactionPayload.MetadataEntry - 55, // 70: ledger.LedgerAction.create_transaction:type_name -> ledger.CreateTransactionPayload - 160, // 71: ledger.LedgerAction.add_metadata:type_name -> common.SaveMetadataCommand - 56, // 72: ledger.LedgerAction.revert_transaction:type_name -> ledger.RevertTransactionPayload - 161, // 73: ledger.LedgerAction.delete_metadata:type_name -> common.DeleteMetadataCommand - 59, // 74: ledger.LedgerAction.add_account_type:type_name -> ledger.AddAccountTypeRequest - 60, // 75: ledger.LedgerAction.remove_account_type:type_name -> ledger.RemoveAccountTypeRequest - 61, // 76: ledger.LedgerAction.set_default_enforcement_mode:type_name -> ledger.SetDefaultEnforcementModeRequest - 57, // 77: ledger.LedgerApplyRequest.action:type_name -> ledger.LedgerAction - 162, // 78: ledger.AddAccountTypeRequest.account_type:type_name -> common.AccountType - 148, // 79: ledger.SetDefaultEnforcementModeRequest.enforcement_mode:type_name -> common.ChartEnforcementMode - 148, // 80: ledger.SetDefaultEnforcementModeLedgerRequest.enforcement_mode:type_name -> common.ChartEnforcementMode - 162, // 81: ledger.AddAccountTypeLedgerRequest.account_type:type_name -> common.AccountType - 69, // 82: ledger.GetPrimaryMetricsResponse.metrics:type_name -> ledger.PebbleMetrics - 69, // 83: ledger.GetSecondaryMetricsResponse.metrics:type_name -> ledger.PebbleMetrics - 70, // 84: ledger.PebbleMetrics.block_cache:type_name -> ledger.BlockCacheMetrics - 71, // 85: ledger.PebbleMetrics.compact:type_name -> ledger.CompactMetrics - 72, // 86: ledger.PebbleMetrics.flush:type_name -> ledger.FlushMetrics - 73, // 87: ledger.PebbleMetrics.mem_table:type_name -> ledger.MemTableMetrics - 74, // 88: ledger.PebbleMetrics.snapshots:type_name -> ledger.SnapshotsMetrics - 75, // 89: ledger.PebbleMetrics.table:type_name -> ledger.TableMetrics - 76, // 90: ledger.PebbleMetrics.table_cache:type_name -> ledger.TableCacheMetrics - 77, // 91: ledger.PebbleMetrics.wal:type_name -> ledger.WALMetrics - 78, // 92: ledger.PebbleMetrics.keys:type_name -> ledger.KeysMetrics - 79, // 93: ledger.PebbleMetrics.levels:type_name -> ledger.LevelMetrics - 82, // 94: ledger.CheckStoreEvent.error:type_name -> ledger.CheckStoreError - 83, // 95: ledger.CheckStoreEvent.progress:type_name -> ledger.CheckStoreProgress + 135, // 49: ledger.SaveLedgerMetadataRequest.metadata:type_name -> ledger.SaveLedgerMetadataRequest.MetadataEntry + 154, // 50: ledger.AddEventsSinkRequest.config:type_name -> common.SinkConfig + 145, // 51: ledger.ListSigningKeysRequest.options:type_name -> common.ListOptions + 145, // 52: ledger.ListChaptersRequest.options:type_name -> common.ListOptions + 155, // 53: ledger.SetMetadataFieldTypeRequest.target_type:type_name -> common.TargetType + 156, // 54: ledger.SetMetadataFieldTypeRequest.type:type_name -> common.MetadataType + 155, // 55: ledger.RemoveMetadataFieldTypeRequest.target_type:type_name -> common.TargetType + 157, // 56: ledger.CreateIndexRequest.id:type_name -> common.IndexID + 157, // 57: ledger.DropIndexRequest.id:type_name -> common.IndexID + 150, // 58: ledger.GetNumscriptRequest.read:type_name -> common.ReadOptions + 145, // 59: ledger.ListNumscriptsRequest.options:type_name -> common.ListOptions + 136, // 60: ledger.ScriptReference.vars:type_name -> ledger.ScriptReference.VarsEntry + 55, // 61: ledger.DiscoveryResponse.response_signing:type_name -> ledger.ResponseSigningInfo + 53, // 62: ledger.DiscoveryResponse.server_info:type_name -> ledger.ServerInfo + 158, // 63: ledger.CreateTransactionPayload.postings:type_name -> common.Posting + 159, // 64: ledger.CreateTransactionPayload.script:type_name -> common.Script + 160, // 65: ledger.CreateTransactionPayload.timestamp:type_name -> common.Timestamp + 137, // 66: ledger.CreateTransactionPayload.metadata:type_name -> ledger.CreateTransactionPayload.MetadataEntry + 138, // 67: ledger.CreateTransactionPayload.account_metadata:type_name -> ledger.CreateTransactionPayload.AccountMetadataEntry + 47, // 68: ledger.CreateTransactionPayload.script_reference:type_name -> ledger.ScriptReference + 139, // 69: ledger.RevertTransactionPayload.metadata:type_name -> ledger.RevertTransactionPayload.MetadataEntry + 56, // 70: ledger.LedgerAction.create_transaction:type_name -> ledger.CreateTransactionPayload + 161, // 71: ledger.LedgerAction.add_metadata:type_name -> common.SaveMetadataCommand + 57, // 72: ledger.LedgerAction.revert_transaction:type_name -> ledger.RevertTransactionPayload + 162, // 73: ledger.LedgerAction.delete_metadata:type_name -> common.DeleteMetadataCommand + 60, // 74: ledger.LedgerAction.add_account_type:type_name -> ledger.AddAccountTypeRequest + 61, // 75: ledger.LedgerAction.remove_account_type:type_name -> ledger.RemoveAccountTypeRequest + 62, // 76: ledger.LedgerAction.set_default_enforcement_mode:type_name -> ledger.SetDefaultEnforcementModeRequest + 58, // 77: ledger.LedgerApplyRequest.action:type_name -> ledger.LedgerAction + 163, // 78: ledger.AddAccountTypeRequest.account_type:type_name -> common.AccountType + 149, // 79: ledger.SetDefaultEnforcementModeRequest.enforcement_mode:type_name -> common.ChartEnforcementMode + 149, // 80: ledger.SetDefaultEnforcementModeLedgerRequest.enforcement_mode:type_name -> common.ChartEnforcementMode + 163, // 81: ledger.AddAccountTypeLedgerRequest.account_type:type_name -> common.AccountType + 70, // 82: ledger.GetPrimaryMetricsResponse.metrics:type_name -> ledger.PebbleMetrics + 70, // 83: ledger.GetSecondaryMetricsResponse.metrics:type_name -> ledger.PebbleMetrics + 71, // 84: ledger.PebbleMetrics.block_cache:type_name -> ledger.BlockCacheMetrics + 72, // 85: ledger.PebbleMetrics.compact:type_name -> ledger.CompactMetrics + 73, // 86: ledger.PebbleMetrics.flush:type_name -> ledger.FlushMetrics + 74, // 87: ledger.PebbleMetrics.mem_table:type_name -> ledger.MemTableMetrics + 75, // 88: ledger.PebbleMetrics.snapshots:type_name -> ledger.SnapshotsMetrics + 76, // 89: ledger.PebbleMetrics.table:type_name -> ledger.TableMetrics + 77, // 90: ledger.PebbleMetrics.table_cache:type_name -> ledger.TableCacheMetrics + 78, // 91: ledger.PebbleMetrics.wal:type_name -> ledger.WALMetrics + 79, // 92: ledger.PebbleMetrics.keys:type_name -> ledger.KeysMetrics + 80, // 93: ledger.PebbleMetrics.levels:type_name -> ledger.LevelMetrics + 83, // 94: ledger.CheckStoreEvent.error:type_name -> ledger.CheckStoreError + 84, // 95: ledger.CheckStoreEvent.progress:type_name -> ledger.CheckStoreProgress 0, // 96: ledger.CheckStoreError.error_type:type_name -> ledger.CheckStoreErrorType - 144, // 97: ledger.ListAuditEntriesRequest.options:type_name -> common.ListOptions - 144, // 98: ledger.ListLogsRequest.options:type_name -> common.ListOptions - 153, // 99: ledger.GetEventsSinksResponse.sinks:type_name -> common.SinkConfig - 163, // 100: ledger.GetEventsSinksResponse.sink_statuses:type_name -> common.SinkStatus - 139, // 101: ledger.GetMetadataSchemaStatusResponse.account_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry - 140, // 102: ledger.GetMetadataSchemaStatusResponse.transaction_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry - 141, // 103: ledger.GetMetadataSchemaStatusResponse.ledger_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry - 155, // 104: ledger.MetadataFieldStatus.declared_type:type_name -> common.MetadataType - 98, // 105: ledger.AnalyzeAccountsResponse.patterns:type_name -> ledger.AccountPattern - 95, // 106: ledger.AnalyzeAccountsEvent.progress:type_name -> ledger.AnalyzeProgress - 94, // 107: ledger.AnalyzeAccountsEvent.result:type_name -> ledger.AnalyzeAccountsResponse - 95, // 108: ledger.AnalyzeTransactionsEvent.progress:type_name -> ledger.AnalyzeProgress - 101, // 109: ledger.AnalyzeTransactionsEvent.result:type_name -> ledger.AnalyzeTransactionsResponse - 99, // 110: ledger.AccountPattern.segments:type_name -> ledger.PatternSegment + 145, // 97: ledger.ListAuditEntriesRequest.options:type_name -> common.ListOptions + 145, // 98: ledger.ListLogsRequest.options:type_name -> common.ListOptions + 154, // 99: ledger.GetEventsSinksResponse.sinks:type_name -> common.SinkConfig + 164, // 100: ledger.GetEventsSinksResponse.sink_statuses:type_name -> common.SinkStatus + 140, // 101: ledger.GetMetadataSchemaStatusResponse.account_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry + 141, // 102: ledger.GetMetadataSchemaStatusResponse.transaction_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry + 142, // 103: ledger.GetMetadataSchemaStatusResponse.ledger_fields:type_name -> ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry + 156, // 104: ledger.MetadataFieldStatus.declared_type:type_name -> common.MetadataType + 99, // 105: ledger.AnalyzeAccountsResponse.patterns:type_name -> ledger.AccountPattern + 96, // 106: ledger.AnalyzeAccountsEvent.progress:type_name -> ledger.AnalyzeProgress + 95, // 107: ledger.AnalyzeAccountsEvent.result:type_name -> ledger.AnalyzeAccountsResponse + 96, // 108: ledger.AnalyzeTransactionsEvent.progress:type_name -> ledger.AnalyzeProgress + 102, // 109: ledger.AnalyzeTransactionsEvent.result:type_name -> ledger.AnalyzeTransactionsResponse + 100, // 110: ledger.AccountPattern.segments:type_name -> ledger.PatternSegment 1, // 111: ledger.PatternSegment.type:type_name -> ledger.PatternSegmentType - 102, // 112: ledger.AnalyzeTransactionsResponse.flow_patterns:type_name -> ledger.FlowPattern + 103, // 112: ledger.AnalyzeTransactionsResponse.flow_patterns:type_name -> ledger.FlowPattern 2, // 113: ledger.FlowPattern.structure:type_name -> ledger.PostingStructure - 103, // 114: ledger.FlowPattern.postings:type_name -> ledger.NormalizedPosting - 104, // 115: ledger.FlowPattern.temporal:type_name -> ledger.TemporalStats - 106, // 116: ledger.FlowPattern.volume_stats:type_name -> ledger.AssetVolumeStats - 159, // 117: ledger.TemporalStats.first_seen:type_name -> common.Timestamp - 159, // 118: ledger.TemporalStats.last_seen:type_name -> common.Timestamp - 105, // 119: ledger.TemporalStats.peak_hours:type_name -> ledger.HourBucket - 164, // 120: ledger.CreatePreparedQueryRequest.query:type_name -> common.PreparedQuery - 165, // 121: ledger.UpdatePreparedQueryRequest.filter:type_name -> common.QueryFilter - 164, // 122: ledger.ListPreparedQueriesResponse.queries:type_name -> common.PreparedQuery - 142, // 123: ledger.ExecutePreparedQueryRequest.parameters:type_name -> ledger.ExecutePreparedQueryRequest.ParametersEntry - 166, // 124: ledger.ExecutePreparedQueryRequest.mode:type_name -> common.QueryMode - 167, // 125: ledger.ExecutePreparedQueryResponse.cursor:type_name -> common.PreparedQueryCursor - 168, // 126: ledger.ExecutePreparedQueryResponse.aggregate:type_name -> common.AggregateResult - 119, // 127: ledger.GetIndexStatusResponse.indexes:type_name -> ledger.IndexEntry - 169, // 128: ledger.IndexEntry.index:type_name -> common.Index + 104, // 114: ledger.FlowPattern.postings:type_name -> ledger.NormalizedPosting + 105, // 115: ledger.FlowPattern.temporal:type_name -> ledger.TemporalStats + 107, // 116: ledger.FlowPattern.volume_stats:type_name -> ledger.AssetVolumeStats + 160, // 117: ledger.TemporalStats.first_seen:type_name -> common.Timestamp + 160, // 118: ledger.TemporalStats.last_seen:type_name -> common.Timestamp + 106, // 119: ledger.TemporalStats.peak_hours:type_name -> ledger.HourBucket + 165, // 120: ledger.CreatePreparedQueryRequest.query:type_name -> common.PreparedQuery + 166, // 121: ledger.UpdatePreparedQueryRequest.filter:type_name -> common.QueryFilter + 165, // 122: ledger.ListPreparedQueriesResponse.queries:type_name -> common.PreparedQuery + 143, // 123: ledger.ExecutePreparedQueryRequest.parameters:type_name -> ledger.ExecutePreparedQueryRequest.ParametersEntry + 167, // 124: ledger.ExecutePreparedQueryRequest.mode:type_name -> common.QueryMode + 168, // 125: ledger.ExecutePreparedQueryResponse.cursor:type_name -> common.PreparedQueryCursor + 169, // 126: ledger.ExecutePreparedQueryResponse.aggregate:type_name -> common.AggregateResult + 120, // 127: ledger.GetIndexStatusResponse.indexes:type_name -> ledger.IndexEntry + 170, // 128: ledger.IndexEntry.index:type_name -> common.Index 4, // 129: ledger.ListIndexesRequest.scope:type_name -> ledger.ListIndexesRequest.Scope - 165, // 130: ledger.AggregateVolumesRequest.filter:type_name -> common.QueryFilter - 124, // 131: ledger.QueryProfile.root_iterator:type_name -> ledger.IteratorProfile - 124, // 132: ledger.IteratorProfile.children:type_name -> ledger.IteratorProfile - 154, // 133: ledger.InspectIndexRequest.target_type:type_name -> common.TargetType + 166, // 130: ledger.AggregateVolumesRequest.filter:type_name -> common.QueryFilter + 125, // 131: ledger.QueryProfile.root_iterator:type_name -> ledger.IteratorProfile + 125, // 132: ledger.IteratorProfile.children:type_name -> ledger.IteratorProfile + 155, // 133: ledger.InspectIndexRequest.target_type:type_name -> common.TargetType 3, // 134: ledger.InspectIndexRequest.mode:type_name -> ledger.InspectIndexMode - 127, // 135: ledger.InspectIndexResponse.distinct_values:type_name -> ledger.InspectDistinctValues - 129, // 136: ledger.InspectIndexResponse.facets:type_name -> ledger.InspectFacets - 130, // 137: ledger.InspectIndexResponse.summary:type_name -> ledger.InspectSummary - 170, // 138: ledger.InspectDistinctValues.values:type_name -> common.MetadataValue - 170, // 139: ledger.InspectFacet.value:type_name -> common.MetadataValue - 128, // 140: ledger.InspectFacets.facets:type_name -> ledger.InspectFacet - 170, // 141: ledger.InspectSummary.min:type_name -> common.MetadataValue - 170, // 142: ledger.InspectSummary.max:type_name -> common.MetadataValue - 162, // 143: ledger.CreateLedgerRequest.AccountTypesEntry.value:type_name -> common.AccountType - 170, // 144: ledger.SaveLedgerMetadataRequest.MetadataEntry.value:type_name -> common.MetadataValue - 170, // 145: ledger.CreateTransactionPayload.MetadataEntry.value:type_name -> common.MetadataValue - 171, // 146: ledger.CreateTransactionPayload.AccountMetadataEntry.value:type_name -> common.MetadataMap - 170, // 147: ledger.RevertTransactionPayload.MetadataEntry.value:type_name -> common.MetadataValue - 92, // 148: ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry.value:type_name -> ledger.MetadataFieldStatus - 92, // 149: ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry.value:type_name -> ledger.MetadataFieldStatus - 92, // 150: ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry.value:type_name -> ledger.MetadataFieldStatus - 172, // 151: ledger.ExecutePreparedQueryRequest.ParametersEntry.value:type_name -> common.ParameterValue + 128, // 135: ledger.InspectIndexResponse.distinct_values:type_name -> ledger.InspectDistinctValues + 130, // 136: ledger.InspectIndexResponse.facets:type_name -> ledger.InspectFacets + 131, // 137: ledger.InspectIndexResponse.summary:type_name -> ledger.InspectSummary + 171, // 138: ledger.InspectDistinctValues.values:type_name -> common.MetadataValue + 171, // 139: ledger.InspectFacet.value:type_name -> common.MetadataValue + 129, // 140: ledger.InspectFacets.facets:type_name -> ledger.InspectFacet + 171, // 141: ledger.InspectSummary.min:type_name -> common.MetadataValue + 171, // 142: ledger.InspectSummary.max:type_name -> common.MetadataValue + 163, // 143: ledger.CreateLedgerRequest.AccountTypesEntry.value:type_name -> common.AccountType + 171, // 144: ledger.SaveLedgerMetadataRequest.MetadataEntry.value:type_name -> common.MetadataValue + 171, // 145: ledger.CreateTransactionPayload.MetadataEntry.value:type_name -> common.MetadataValue + 172, // 146: ledger.CreateTransactionPayload.AccountMetadataEntry.value:type_name -> common.MetadataMap + 171, // 147: ledger.RevertTransactionPayload.MetadataEntry.value:type_name -> common.MetadataValue + 93, // 148: ledger.GetMetadataSchemaStatusResponse.AccountFieldsEntry.value:type_name -> ledger.MetadataFieldStatus + 93, // 149: ledger.GetMetadataSchemaStatusResponse.TransactionFieldsEntry.value:type_name -> ledger.MetadataFieldStatus + 93, // 150: ledger.GetMetadataSchemaStatusResponse.LedgerFieldsEntry.value:type_name -> ledger.MetadataFieldStatus + 173, // 151: ledger.ExecutePreparedQueryRequest.ParametersEntry.value:type_name -> common.ParameterValue 13, // 152: ledger.BucketService.ListLedgers:input_type -> ledger.ListLedgersRequest 14, // 153: ledger.BucketService.GetLedger:input_type -> ledger.GetLedgerRequest 5, // 154: ledger.BucketService.GetAccount:input_type -> ledger.GetAccountRequest @@ -9701,71 +9763,73 @@ var file_bucket_proto_depIdxs = []int32{ 8, // 156: ledger.BucketService.ListTransactions:input_type -> ledger.ListTransactionsRequest 9, // 157: ledger.BucketService.ListAccounts:input_type -> ledger.ListAccountsRequest 15, // 158: ledger.BucketService.Apply:input_type -> ledger.ApplyRequest - 65, // 159: ledger.BucketService.GetPrimaryMetrics:input_type -> ledger.GetPrimaryMetricsRequest - 67, // 160: ledger.BucketService.GetSecondaryMetrics:input_type -> ledger.GetSecondaryMetricsRequest - 80, // 161: ledger.BucketService.CheckStore:input_type -> ledger.CheckStoreRequest - 84, // 162: ledger.BucketService.ListAuditEntries:input_type -> ledger.ListAuditEntriesRequest - 85, // 163: ledger.BucketService.GetAuditEntry:input_type -> ledger.GetAuditEntryRequest - 88, // 164: ledger.BucketService.GetEventsSinks:input_type -> ledger.GetEventsSinksRequest + 66, // 159: ledger.BucketService.GetPrimaryMetrics:input_type -> ledger.GetPrimaryMetricsRequest + 68, // 160: ledger.BucketService.GetSecondaryMetrics:input_type -> ledger.GetSecondaryMetricsRequest + 81, // 161: ledger.BucketService.CheckStore:input_type -> ledger.CheckStoreRequest + 85, // 162: ledger.BucketService.ListAuditEntries:input_type -> ledger.ListAuditEntriesRequest + 86, // 163: ledger.BucketService.GetAuditEntry:input_type -> ledger.GetAuditEntryRequest + 89, // 164: ledger.BucketService.GetEventsSinks:input_type -> ledger.GetEventsSinksRequest 34, // 165: ledger.BucketService.ListChapters:input_type -> ledger.ListChaptersRequest - 86, // 166: ledger.BucketService.ListLogs:input_type -> ledger.ListLogsRequest - 87, // 167: ledger.BucketService.GetLog:input_type -> ledger.GetLogRequest - 49, // 168: ledger.BucketService.GetChapterSchedule:input_type -> ledger.GetChapterScheduleRequest + 87, // 166: ledger.BucketService.ListLogs:input_type -> ledger.ListLogsRequest + 88, // 167: ledger.BucketService.GetLog:input_type -> ledger.GetLogRequest + 50, // 168: ledger.BucketService.GetChapterSchedule:input_type -> ledger.GetChapterScheduleRequest 29, // 169: ledger.BucketService.ListSigningKeys:input_type -> ledger.ListSigningKeysRequest - 51, // 170: ledger.BucketService.Discovery:input_type -> ledger.DiscoveryRequest - 90, // 171: ledger.BucketService.GetMetadataSchemaStatus:input_type -> ledger.GetMetadataSchemaStatusRequest - 93, // 172: ledger.BucketService.AnalyzeAccounts:input_type -> ledger.AnalyzeAccountsRequest - 100, // 173: ledger.BucketService.AnalyzeTransactions:input_type -> ledger.AnalyzeTransactionsRequest - 107, // 174: ledger.BucketService.CreatePreparedQuery:input_type -> ledger.CreatePreparedQueryRequest - 109, // 175: ledger.BucketService.UpdatePreparedQuery:input_type -> ledger.UpdatePreparedQueryRequest - 111, // 176: ledger.BucketService.DeletePreparedQuery:input_type -> ledger.DeletePreparedQueryRequest - 113, // 177: ledger.BucketService.ListPreparedQueries:input_type -> ledger.ListPreparedQueriesRequest - 115, // 178: ledger.BucketService.ExecutePreparedQuery:input_type -> ledger.ExecutePreparedQueryRequest - 117, // 179: ledger.BucketService.GetIndexStatus:input_type -> ledger.GetIndexStatusRequest - 120, // 180: ledger.BucketService.ListIndexes:input_type -> ledger.ListIndexesRequest - 121, // 181: ledger.BucketService.GetLedgerStats:input_type -> ledger.GetLedgerStatsRequest - 122, // 182: ledger.BucketService.AggregateVolumes:input_type -> ledger.AggregateVolumesRequest + 52, // 170: ledger.BucketService.Discovery:input_type -> ledger.DiscoveryRequest + 91, // 171: ledger.BucketService.GetMetadataSchemaStatus:input_type -> ledger.GetMetadataSchemaStatusRequest + 94, // 172: ledger.BucketService.AnalyzeAccounts:input_type -> ledger.AnalyzeAccountsRequest + 101, // 173: ledger.BucketService.AnalyzeTransactions:input_type -> ledger.AnalyzeTransactionsRequest + 108, // 174: ledger.BucketService.CreatePreparedQuery:input_type -> ledger.CreatePreparedQueryRequest + 110, // 175: ledger.BucketService.UpdatePreparedQuery:input_type -> ledger.UpdatePreparedQueryRequest + 112, // 176: ledger.BucketService.DeletePreparedQuery:input_type -> ledger.DeletePreparedQueryRequest + 114, // 177: ledger.BucketService.ListPreparedQueries:input_type -> ledger.ListPreparedQueriesRequest + 116, // 178: ledger.BucketService.ExecutePreparedQuery:input_type -> ledger.ExecutePreparedQueryRequest + 118, // 179: ledger.BucketService.GetIndexStatus:input_type -> ledger.GetIndexStatusRequest + 121, // 180: ledger.BucketService.ListIndexes:input_type -> ledger.ListIndexesRequest + 122, // 181: ledger.BucketService.GetLedgerStats:input_type -> ledger.GetLedgerStatsRequest + 123, // 182: ledger.BucketService.AggregateVolumes:input_type -> ledger.AggregateVolumesRequest 44, // 183: ledger.BucketService.GetNumscript:input_type -> ledger.GetNumscriptRequest 45, // 184: ledger.BucketService.ListNumscripts:input_type -> ledger.ListNumscriptsRequest - 125, // 185: ledger.BucketService.InspectIndex:input_type -> ledger.InspectIndexRequest - 131, // 186: ledger.BucketService.Barrier:input_type -> ledger.BarrierRequest - 173, // 187: ledger.BucketService.ListLedgers:output_type -> common.LedgerInfo - 173, // 188: ledger.BucketService.GetLedger:output_type -> common.LedgerInfo - 174, // 189: ledger.BucketService.GetAccount:output_type -> common.Account - 7, // 190: ledger.BucketService.GetTransaction:output_type -> ledger.GetTransactionResponse - 143, // 191: ledger.BucketService.ListTransactions:output_type -> common.Transaction - 174, // 192: ledger.BucketService.ListAccounts:output_type -> common.Account - 17, // 193: ledger.BucketService.Apply:output_type -> ledger.ApplyResponse - 66, // 194: ledger.BucketService.GetPrimaryMetrics:output_type -> ledger.GetPrimaryMetricsResponse - 68, // 195: ledger.BucketService.GetSecondaryMetrics:output_type -> ledger.GetSecondaryMetricsResponse - 81, // 196: ledger.BucketService.CheckStore:output_type -> ledger.CheckStoreEvent - 175, // 197: ledger.BucketService.ListAuditEntries:output_type -> audit.AuditEntry - 175, // 198: ledger.BucketService.GetAuditEntry:output_type -> audit.AuditEntry - 89, // 199: ledger.BucketService.GetEventsSinks:output_type -> ledger.GetEventsSinksResponse - 176, // 200: ledger.BucketService.ListChapters:output_type -> common.Chapter - 152, // 201: ledger.BucketService.ListLogs:output_type -> common.Log - 152, // 202: ledger.BucketService.GetLog:output_type -> common.Log - 50, // 203: ledger.BucketService.GetChapterSchedule:output_type -> ledger.GetChapterScheduleResponse - 177, // 204: ledger.BucketService.ListSigningKeys:output_type -> common.SigningKey - 53, // 205: ledger.BucketService.Discovery:output_type -> ledger.DiscoveryResponse - 91, // 206: ledger.BucketService.GetMetadataSchemaStatus:output_type -> ledger.GetMetadataSchemaStatusResponse - 96, // 207: ledger.BucketService.AnalyzeAccounts:output_type -> ledger.AnalyzeAccountsEvent - 97, // 208: ledger.BucketService.AnalyzeTransactions:output_type -> ledger.AnalyzeTransactionsEvent - 108, // 209: ledger.BucketService.CreatePreparedQuery:output_type -> ledger.CreatePreparedQueryResponse - 110, // 210: ledger.BucketService.UpdatePreparedQuery:output_type -> ledger.UpdatePreparedQueryResponse - 112, // 211: ledger.BucketService.DeletePreparedQuery:output_type -> ledger.DeletePreparedQueryResponse - 114, // 212: ledger.BucketService.ListPreparedQueries:output_type -> ledger.ListPreparedQueriesResponse - 116, // 213: ledger.BucketService.ExecutePreparedQuery:output_type -> ledger.ExecutePreparedQueryResponse - 118, // 214: ledger.BucketService.GetIndexStatus:output_type -> ledger.GetIndexStatusResponse - 169, // 215: ledger.BucketService.ListIndexes:output_type -> common.Index - 178, // 216: ledger.BucketService.GetLedgerStats:output_type -> common.LedgerStats - 168, // 217: ledger.BucketService.AggregateVolumes:output_type -> common.AggregateResult - 179, // 218: ledger.BucketService.GetNumscript:output_type -> common.NumscriptInfo - 179, // 219: ledger.BucketService.ListNumscripts:output_type -> common.NumscriptInfo - 126, // 220: ledger.BucketService.InspectIndex:output_type -> ledger.InspectIndexResponse - 132, // 221: ledger.BucketService.Barrier:output_type -> ledger.BarrierResponse - 187, // [187:222] is the sub-list for method output_type - 152, // [152:187] is the sub-list for method input_type + 46, // 185: ledger.BucketService.GetTemplateUsage:input_type -> ledger.GetTemplateUsageRequest + 126, // 186: ledger.BucketService.InspectIndex:input_type -> ledger.InspectIndexRequest + 132, // 187: ledger.BucketService.Barrier:input_type -> ledger.BarrierRequest + 174, // 188: ledger.BucketService.ListLedgers:output_type -> common.LedgerInfo + 174, // 189: ledger.BucketService.GetLedger:output_type -> common.LedgerInfo + 175, // 190: ledger.BucketService.GetAccount:output_type -> common.Account + 7, // 191: ledger.BucketService.GetTransaction:output_type -> ledger.GetTransactionResponse + 144, // 192: ledger.BucketService.ListTransactions:output_type -> common.Transaction + 175, // 193: ledger.BucketService.ListAccounts:output_type -> common.Account + 17, // 194: ledger.BucketService.Apply:output_type -> ledger.ApplyResponse + 67, // 195: ledger.BucketService.GetPrimaryMetrics:output_type -> ledger.GetPrimaryMetricsResponse + 69, // 196: ledger.BucketService.GetSecondaryMetrics:output_type -> ledger.GetSecondaryMetricsResponse + 82, // 197: ledger.BucketService.CheckStore:output_type -> ledger.CheckStoreEvent + 176, // 198: ledger.BucketService.ListAuditEntries:output_type -> audit.AuditEntry + 176, // 199: ledger.BucketService.GetAuditEntry:output_type -> audit.AuditEntry + 90, // 200: ledger.BucketService.GetEventsSinks:output_type -> ledger.GetEventsSinksResponse + 177, // 201: ledger.BucketService.ListChapters:output_type -> common.Chapter + 153, // 202: ledger.BucketService.ListLogs:output_type -> common.Log + 153, // 203: ledger.BucketService.GetLog:output_type -> common.Log + 51, // 204: ledger.BucketService.GetChapterSchedule:output_type -> ledger.GetChapterScheduleResponse + 178, // 205: ledger.BucketService.ListSigningKeys:output_type -> common.SigningKey + 54, // 206: ledger.BucketService.Discovery:output_type -> ledger.DiscoveryResponse + 92, // 207: ledger.BucketService.GetMetadataSchemaStatus:output_type -> ledger.GetMetadataSchemaStatusResponse + 97, // 208: ledger.BucketService.AnalyzeAccounts:output_type -> ledger.AnalyzeAccountsEvent + 98, // 209: ledger.BucketService.AnalyzeTransactions:output_type -> ledger.AnalyzeTransactionsEvent + 109, // 210: ledger.BucketService.CreatePreparedQuery:output_type -> ledger.CreatePreparedQueryResponse + 111, // 211: ledger.BucketService.UpdatePreparedQuery:output_type -> ledger.UpdatePreparedQueryResponse + 113, // 212: ledger.BucketService.DeletePreparedQuery:output_type -> ledger.DeletePreparedQueryResponse + 115, // 213: ledger.BucketService.ListPreparedQueries:output_type -> ledger.ListPreparedQueriesResponse + 117, // 214: ledger.BucketService.ExecutePreparedQuery:output_type -> ledger.ExecutePreparedQueryResponse + 119, // 215: ledger.BucketService.GetIndexStatus:output_type -> ledger.GetIndexStatusResponse + 170, // 216: ledger.BucketService.ListIndexes:output_type -> common.Index + 179, // 217: ledger.BucketService.GetLedgerStats:output_type -> common.LedgerStats + 169, // 218: ledger.BucketService.AggregateVolumes:output_type -> common.AggregateResult + 180, // 219: ledger.BucketService.GetNumscript:output_type -> common.NumscriptInfo + 180, // 220: ledger.BucketService.ListNumscripts:output_type -> common.NumscriptInfo + 181, // 221: ledger.BucketService.GetTemplateUsage:output_type -> common.TemplateUsage + 127, // 222: ledger.BucketService.InspectIndex:output_type -> ledger.InspectIndexResponse + 133, // 223: ledger.BucketService.Barrier:output_type -> ledger.BarrierResponse + 188, // [188:224] is the sub-list for method output_type + 152, // [152:188] is the sub-list for method input_type 152, // [152:152] is the sub-list for extension type_name 152, // [152:152] is the sub-list for extension extendee 0, // [0:152] is the sub-list for field type_name @@ -9816,7 +9880,7 @@ func file_bucket_proto_init() { (*Request_SaveLedgerMetadata)(nil), (*Request_DeleteLedgerMetadata)(nil), } - file_bucket_proto_msgTypes[52].OneofWrappers = []any{ + file_bucket_proto_msgTypes[53].OneofWrappers = []any{ (*LedgerAction_CreateTransaction)(nil), (*LedgerAction_AddMetadata)(nil), (*LedgerAction_RevertTransaction)(nil), @@ -9825,23 +9889,23 @@ func file_bucket_proto_init() { (*LedgerAction_RemoveAccountType)(nil), (*LedgerAction_SetDefaultEnforcementMode)(nil), } - file_bucket_proto_msgTypes[76].OneofWrappers = []any{ + file_bucket_proto_msgTypes[77].OneofWrappers = []any{ (*CheckStoreEvent_Error)(nil), (*CheckStoreEvent_Progress)(nil), } - file_bucket_proto_msgTypes[91].OneofWrappers = []any{ + file_bucket_proto_msgTypes[92].OneofWrappers = []any{ (*AnalyzeAccountsEvent_Progress)(nil), (*AnalyzeAccountsEvent_Result)(nil), } - file_bucket_proto_msgTypes[92].OneofWrappers = []any{ + file_bucket_proto_msgTypes[93].OneofWrappers = []any{ (*AnalyzeTransactionsEvent_Progress)(nil), (*AnalyzeTransactionsEvent_Result)(nil), } - file_bucket_proto_msgTypes[111].OneofWrappers = []any{ + file_bucket_proto_msgTypes[112].OneofWrappers = []any{ (*ExecutePreparedQueryResponse_Cursor)(nil), (*ExecutePreparedQueryResponse_Aggregate)(nil), } - file_bucket_proto_msgTypes[121].OneofWrappers = []any{ + file_bucket_proto_msgTypes[122].OneofWrappers = []any{ (*InspectIndexResponse_DistinctValues)(nil), (*InspectIndexResponse_Facets)(nil), (*InspectIndexResponse_Summary)(nil), @@ -9852,7 +9916,7 @@ func file_bucket_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_bucket_proto_rawDesc), len(file_bucket_proto_rawDesc)), NumEnums: 5, - NumMessages: 138, + NumMessages: 139, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/proto/servicepb/bucket_dethash.pb.go b/internal/proto/servicepb/bucket_dethash.pb.go index 361cf54a61..03dfd439e1 100644 --- a/internal/proto/servicepb/bucket_dethash.pb.go +++ b/internal/proto/servicepb/bucket_dethash.pb.go @@ -1036,6 +1036,17 @@ func (m *ListNumscriptsRequest) MarshalDeterministicVT(dAtA []byte) []byte { return append(dAtA, b...) } +func (m *GetTemplateUsageRequest) MarshalDeterministicVT(dAtA []byte) []byte { + if m == nil { + return dAtA + } + b, err := m.MarshalVT() + if err != nil { + panic("MarshalDeterministicVT: " + err.Error()) + } + return append(dAtA, b...) +} + func (m *ScriptReference) MarshalDeterministicVT(dAtA []byte) []byte { if m == nil { return dAtA diff --git a/internal/proto/servicepb/bucket_grpc.pb.go b/internal/proto/servicepb/bucket_grpc.pb.go index 76cca762b7..b4f6e77bbc 100644 --- a/internal/proto/servicepb/bucket_grpc.pb.go +++ b/internal/proto/servicepb/bucket_grpc.pb.go @@ -54,6 +54,7 @@ const ( BucketService_AggregateVolumes_FullMethodName = "/ledger.BucketService/AggregateVolumes" BucketService_GetNumscript_FullMethodName = "/ledger.BucketService/GetNumscript" BucketService_ListNumscripts_FullMethodName = "/ledger.BucketService/ListNumscripts" + BucketService_GetTemplateUsage_FullMethodName = "/ledger.BucketService/GetTemplateUsage" BucketService_InspectIndex_FullMethodName = "/ledger.BucketService/InspectIndex" BucketService_Barrier_FullMethodName = "/ledger.BucketService/Barrier" ) @@ -131,6 +132,10 @@ type BucketServiceClient interface { GetNumscript(ctx context.Context, in *GetNumscriptRequest, opts ...grpc.CallOption) (*commonpb.NumscriptInfo, error) // ListNumscripts streams all numscripts (latest version of each) ListNumscripts(ctx context.Context, in *ListNumscriptsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[commonpb.NumscriptInfo], error) + // GetTemplateUsage returns per-template invocation usage populated by the + // usagebuilder subsystem. Eventually consistent — may lag the live FSM + // by up to one usagebuilder tick interval. + GetTemplateUsage(ctx context.Context, in *GetTemplateUsageRequest, opts ...grpc.CallOption) (*commonpb.TemplateUsage, error) // InspectIndex scans a metadata index and returns distinct values, facets, or a summary InspectIndex(ctx context.Context, in *InspectIndexRequest, opts ...grpc.CallOption) (*InspectIndexResponse, error) // Barrier proposes a no-op through Raft consensus. When it returns, all @@ -585,6 +590,16 @@ func (c *bucketServiceClient) ListNumscripts(ctx context.Context, in *ListNumscr // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type BucketService_ListNumscriptsClient = grpc.ServerStreamingClient[commonpb.NumscriptInfo] +func (c *bucketServiceClient) GetTemplateUsage(ctx context.Context, in *GetTemplateUsageRequest, opts ...grpc.CallOption) (*commonpb.TemplateUsage, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(commonpb.TemplateUsage) + err := c.cc.Invoke(ctx, BucketService_GetTemplateUsage_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *bucketServiceClient) InspectIndex(ctx context.Context, in *InspectIndexRequest, opts ...grpc.CallOption) (*InspectIndexResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(InspectIndexResponse) @@ -678,6 +693,10 @@ type BucketServiceServer interface { GetNumscript(context.Context, *GetNumscriptRequest) (*commonpb.NumscriptInfo, error) // ListNumscripts streams all numscripts (latest version of each) ListNumscripts(*ListNumscriptsRequest, grpc.ServerStreamingServer[commonpb.NumscriptInfo]) error + // GetTemplateUsage returns per-template invocation usage populated by the + // usagebuilder subsystem. Eventually consistent — may lag the live FSM + // by up to one usagebuilder tick interval. + GetTemplateUsage(context.Context, *GetTemplateUsageRequest) (*commonpb.TemplateUsage, error) // InspectIndex scans a metadata index and returns distinct values, facets, or a summary InspectIndex(context.Context, *InspectIndexRequest) (*InspectIndexResponse, error) // Barrier proposes a no-op through Raft consensus. When it returns, all @@ -793,6 +812,9 @@ func (UnimplementedBucketServiceServer) GetNumscript(context.Context, *GetNumscr func (UnimplementedBucketServiceServer) ListNumscripts(*ListNumscriptsRequest, grpc.ServerStreamingServer[commonpb.NumscriptInfo]) error { return status.Error(codes.Unimplemented, "method ListNumscripts not implemented") } +func (UnimplementedBucketServiceServer) GetTemplateUsage(context.Context, *GetTemplateUsageRequest) (*commonpb.TemplateUsage, error) { + return nil, status.Error(codes.Unimplemented, "method GetTemplateUsage not implemented") +} func (UnimplementedBucketServiceServer) InspectIndex(context.Context, *InspectIndexRequest) (*InspectIndexResponse, error) { return nil, status.Error(codes.Unimplemented, "method InspectIndex not implemented") } @@ -1330,6 +1352,24 @@ func _BucketService_ListNumscripts_Handler(srv interface{}, stream grpc.ServerSt // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type BucketService_ListNumscriptsServer = grpc.ServerStreamingServer[commonpb.NumscriptInfo] +func _BucketService_GetTemplateUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTemplateUsageRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BucketServiceServer).GetTemplateUsage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BucketService_GetTemplateUsage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BucketServiceServer).GetTemplateUsage(ctx, req.(*GetTemplateUsageRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _BucketService_InspectIndex_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(InspectIndexRequest) if err := dec(in); err != nil { @@ -1457,6 +1497,10 @@ var BucketService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetNumscript", Handler: _BucketService_GetNumscript_Handler, }, + { + MethodName: "GetTemplateUsage", + Handler: _BucketService_GetTemplateUsage_Handler, + }, { MethodName: "InspectIndex", Handler: _BucketService_InspectIndex_Handler, diff --git a/internal/proto/servicepb/bucket_reader.pb.go b/internal/proto/servicepb/bucket_reader.pb.go index 1a3967b61e..f1a8369b21 100644 --- a/internal/proto/servicepb/bucket_reader.pb.go +++ b/internal/proto/servicepb/bucket_reader.pb.go @@ -3035,6 +3035,78 @@ func NewListNumscriptsRequestListReader(s []*ListNumscriptsRequest) ListNumscrip return listNumscriptsRequestListReadonly(s) } +// GetTemplateUsageRequestReader provides read-only access to GetTemplateUsageRequest. +// Call Mutate() to obtain a mutable clone. +type GetTemplateUsageRequestReader interface { + GetLedger() string + GetName() string + Mutate() *GetTemplateUsageRequest +} + +type getTemplateUsageRequestReadonly GetTemplateUsageRequest + +func (r *getTemplateUsageRequestReadonly) GetLedger() string { + return (*GetTemplateUsageRequest)(r).GetLedger() +} + +func (r *getTemplateUsageRequestReadonly) GetName() string { + return (*GetTemplateUsageRequest)(r).GetName() +} + +func (r *getTemplateUsageRequestReadonly) Mutate() *GetTemplateUsageRequest { + return (*GetTemplateUsageRequest)(r).CloneVT() +} + +// AsReader returns a read-only view of this GetTemplateUsageRequest. +func (m *GetTemplateUsageRequest) AsReader() GetTemplateUsageRequestReader { + if m == nil { + return nil + } + return (*getTemplateUsageRequestReadonly)(m) +} + +// Mutate returns a mutable deep clone of this GetTemplateUsageRequest. +func (m *GetTemplateUsageRequest) Mutate() *GetTemplateUsageRequest { + return m.CloneVT() +} + +// GetTemplateUsageRequestListReader provides read-only iteration over []*GetTemplateUsageRequest. +type GetTemplateUsageRequestListReader interface { + Len() int + Get(i int) GetTemplateUsageRequestReader + Range(yield func(int, GetTemplateUsageRequestReader) bool) +} + +type getTemplateUsageRequestListReadonly []*GetTemplateUsageRequest + +func (l getTemplateUsageRequestListReadonly) Len() int { return len(l) } + +func (l getTemplateUsageRequestListReadonly) Get(i int) GetTemplateUsageRequestReader { + v := l[i] + if v == nil { + return nil + } + return v.AsReader() +} + +func (l getTemplateUsageRequestListReadonly) Range(yield func(int, GetTemplateUsageRequestReader) bool) { + for i, v := range l { + var r GetTemplateUsageRequestReader + if v != nil { + r = v.AsReader() + } + if !yield(i, r) { + return + } + } +} + +// NewGetTemplateUsageRequestListReader wraps s for read-only iteration. The returned +// view aliases the underlying slice; do not mutate s afterwards. +func NewGetTemplateUsageRequestListReader(s []*GetTemplateUsageRequest) GetTemplateUsageRequestListReader { + return getTemplateUsageRequestListReadonly(s) +} + // ScriptReferenceReader provides read-only access to ScriptReference. // Call Mutate() to obtain a mutable clone. type ScriptReferenceReader interface { diff --git a/internal/proto/servicepb/bucket_vtproto.pb.go b/internal/proto/servicepb/bucket_vtproto.pb.go index e058978d23..2e3b847b5b 100644 --- a/internal/proto/servicepb/bucket_vtproto.pb.go +++ b/internal/proto/servicepb/bucket_vtproto.pb.go @@ -1123,6 +1123,24 @@ func (m *ListNumscriptsRequest) CloneMessageVT() proto.Message { return m.CloneVT() } +func (m *GetTemplateUsageRequest) CloneVT() *GetTemplateUsageRequest { + if m == nil { + return (*GetTemplateUsageRequest)(nil) + } + r := new(GetTemplateUsageRequest) + r.Ledger = m.Ledger + r.Name = m.Name + if len(m.unknownFields) > 0 { + r.unknownFields = make([]byte, len(m.unknownFields)) + copy(r.unknownFields, m.unknownFields) + } + return r +} + +func (m *GetTemplateUsageRequest) CloneMessageVT() proto.Message { + return m.CloneVT() +} + func (m *ScriptReference) CloneVT() *ScriptReference { if m == nil { return (*ScriptReference)(nil) @@ -4993,6 +5011,28 @@ func (this *ListNumscriptsRequest) EqualMessageVT(thatMsg proto.Message) bool { } return this.EqualVT(that) } +func (this *GetTemplateUsageRequest) EqualVT(that *GetTemplateUsageRequest) bool { + if this == that { + return true + } else if this == nil || that == nil { + return false + } + if this.Ledger != that.Ledger { + return false + } + if this.Name != that.Name { + return false + } + return string(this.unknownFields) == string(that.unknownFields) +} + +func (this *GetTemplateUsageRequest) EqualMessageVT(thatMsg proto.Message) bool { + that, ok := thatMsg.(*GetTemplateUsageRequest) + if !ok { + return false + } + return this.EqualVT(that) +} func (this *ScriptReference) EqualVT(that *ScriptReference) bool { if this == that { return true @@ -10732,6 +10772,53 @@ func (m *ListNumscriptsRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *GetTemplateUsageRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetTemplateUsageRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetTemplateUsageRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Name) > 0 { + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x12 + } + if len(m.Ledger) > 0 { + i -= len(m.Ledger) + copy(dAtA[i:], m.Ledger) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Ledger))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *ScriptReference) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -17088,6 +17175,24 @@ func (m *ListNumscriptsRequest) SizeVT() (n int) { return n } +func (m *GetTemplateUsageRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Ledger) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Name) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *ScriptReference) SizeVT() (n int) { if m == nil { return 0 @@ -25061,6 +25166,121 @@ func (m *ListNumscriptsRequest) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *GetTemplateUsageRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetTemplateUsageRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetTemplateUsageRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ledger", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ledger = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ScriptReference) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/internal/storage/usagestore/comparer.go b/internal/storage/usagestore/comparer.go new file mode 100644 index 0000000000..ac9e6992ee --- /dev/null +++ b/internal/storage/usagestore/comparer.go @@ -0,0 +1,127 @@ +package usagestore + +import ( + "bytes" + "encoding/binary" + + "github.com/cockroachdb/pebble/v2" + + "github.com/formancehq/ledger/v3/internal/storage/dal" +) + +// usageStoreComparerName is persisted in the Pebble database. Changing it +// requires rebuilding the usage store from the Raft log. +const usageStoreComparerName = "formance.usagestore.v1" + +// ledgerScopedPrefixLen is the split point for ledger-scoped keys: +// 1 byte prefix + LedgerNameFixedSize bytes ledger name (zero-padded). +const ledgerScopedPrefixLen = 1 + dal.LedgerNameFixedSize + +// UsageStoreComparer splits keys at the [prefix_byte][ledger_name padded 64B] +// boundary so that bloom filters are built on the ledger-scoped prefix rather +// than the full key. Mirrors readstore's comparer semantics — see that file +// for the full rationale. +var UsageStoreComparer = &pebble.Comparer{ + Compare: bytes.Compare, + Equal: bytes.Equal, + ComparePointSuffixes: bytes.Compare, + CompareRangeSuffixes: bytes.Compare, + + AbbreviatedKey: func(key []byte) uint64 { + if len(key) >= 8 { + return binary.BigEndian.Uint64(key) + } + + var v uint64 + for _, b := range key { + v <<= 8 + v |= uint64(b) + } + + return v << uint(8*(8-len(key))) + }, + + FormatKey: pebble.DefaultComparer.FormatKey, + + Separator: func(dst, a, b []byte) []byte { + i := commonPrefixLen(a, b) + dst = append(dst, a...) + + if i == len(a) || i == len(b) { + return dst + } + + if a[i] >= b[i] { + return dst + } + + n := len(dst) - len(a) + if c := a[i] + 1; c < b[i] { + dst[n+i] = c + + return dst[:n+i+1] + } + + return dst + }, + + Successor: func(dst, a []byte) []byte { + for i := range a { + if a[i] != 0xff { + dst = append(dst, a[:i+1]...) + dst[len(dst)-1]++ + + return dst + } + } + + return append(dst, a...) + }, + + Split: usageStoreSplit, + + ImmediateSuccessor: func(dst, prefix []byte) []byte { + dst = append(dst[:0], prefix...) + if len(dst) == ledgerScopedPrefixLen { + dst[len(dst)-1]++ + + return dst + } + + return append(dst, 0x00) + }, + + Name: usageStoreComparerName, +} + +// usageStoreSplit returns the split point for bloom filter prefix extraction. +func usageStoreSplit(key []byte) int { + if len(key) <= 1 { + return len(key) + } + + // Internal singleton keys (non-ledger-scoped) — full key is the prefix. + if key[0] == PrefixInternal { + return len(key) + } + + // Ledger-scoped keys: [prefix_byte][ledger_name padded 64B][...]. + if len(key) >= ledgerScopedPrefixLen { + return ledgerScopedPrefixLen + } + + return len(key) +} + +// commonPrefixLen returns the length of the longest common prefix of a and b. +func commonPrefixLen(a, b []byte) int { + n := min(len(a), len(b)) + + for i := range n { + if a[i] != b[i] { + return i + } + } + + return n +} diff --git a/internal/storage/usagestore/delete_ledger.go b/internal/storage/usagestore/delete_ledger.go new file mode 100644 index 0000000000..98be39a8f0 --- /dev/null +++ b/internal/storage/usagestore/delete_ledger.go @@ -0,0 +1,52 @@ +package usagestore + +import ( + "fmt" + + "github.com/formancehq/ledger/v3/internal/storage/dal" +) + +// ledgerScopedPrefixes lists all usage store key prefixes that contain +// ledger-scoped data (keyed by [prefix][ledgerName padded 64B]...). Every +// entry MUST be wiped by DeleteLedger — missing one leaks rows past a +// ledger drop. +var ledgerScopedPrefixes = [][]byte{ + {PrefixTemplate}, + {PrefixCounter}, +} + +// DeleteLedger removes all usage data for the given ledger. Performs range +// deletes on every ledger-scoped prefix: [prefix][ledgerName padded 64B] -> +// successor of that padded block. +// +// Validation guarantees ledger names are printable ASCII only, so the last +// padding byte is never 0xFF — incrementing it cannot carry past the +// fixed-width name block and yields a clean exclusive upper bound. +func DeleteLedger(batch *dal.WriteSession, ledgerName string) error { + for _, prefix := range ledgerScopedPrefixes { + start := make([]byte, 0, len(prefix)+dal.LedgerNameFixedSize) + start = append(start, prefix...) + start = appendPaddedLedgerName(start, ledgerName) + + end := make([]byte, 0, len(prefix)+dal.LedgerNameFixedSize) + end = append(end, prefix...) + end = appendPaddedLedgerName(end, ledgerName) + end[len(end)-1]++ + + if err := batch.DeleteRangeNoSync(start, end); err != nil { + return fmt.Errorf("deleting usage store prefix %x for ledger %q: %w", prefix, ledgerName, err) + } + } + + return nil +} + +// appendPaddedLedgerName appends the ledger name zero-padded to +// dal.LedgerNameFixedSize bytes. Callers MUST validate the name length +// upstream — copy() truncates silently here. +func appendPaddedLedgerName(dst []byte, name string) []byte { + var pad [dal.LedgerNameFixedSize]byte + copy(pad[:], name) + + return append(dst, pad[:]...) +} diff --git a/internal/storage/usagestore/keys.go b/internal/storage/usagestore/keys.go new file mode 100644 index 0000000000..8404b9d5b7 --- /dev/null +++ b/internal/storage/usagestore/keys.go @@ -0,0 +1,64 @@ +package usagestore + +import ( + "github.com/formancehq/ledger/v3/internal/storage/dal" +) + +// Pebble key prefixes for the usagebuilder's dedicated secondary store. +// All ledger-scoped keys follow [prefix][ledgerName padded 64B][...] so +// the comparer can build bloom filters on the ledger-scoped prefix — same +// pattern as the readstore. +const ( + // PrefixTemplate — per-template usage record. + // Key: [0x01][ledgerName padded 64B][templateName] → TemplateUsage proto. + PrefixTemplate byte = 0x01 + + // PrefixCounter — per-ledger event counter. + // Key: [0x02][ledgerName padded 64B][counterID] → uint64 BE. + PrefixCounter byte = 0x02 + + // PrefixInternal groups all non-ledger-scoped keys under a single prefix + // so Comparer.Split treats them uniformly (full key = prefix). + PrefixInternal byte = 0xFE + + // SubInternalProgress — [0xFE][0x01] → last consumed log sequence (uint64 BE). + SubInternalProgress byte = 0x01 +) + +// Counter IDs identify each per-ledger event counter mirrored by the +// usagebuilder. Values are stable on-disk identifiers — never renumber. +const ( + CounterPosting byte = 0x01 // posting_count + CounterRevert byte = 0x02 // revert_count + CounterNumscriptExecution byte = 0x03 // numscript_execution_count + CounterReference byte = 0x04 // reference_count +) + +// ProgressKey returns the full key for the usagebuilder progress entry. +// +// [0xFE][0x01] +func ProgressKey() []byte { + return []byte{PrefixInternal, SubInternalProgress} +} + +// TemplateUsageKey builds the per-ledger, per-template usage entry key. +// +// [0x01][ledgerName padded 64B][templateName] +func TemplateUsageKey(kb *dal.KeyBuilder, ledgerName, templateName string) []byte { + return kb.Reset(). + PutByte(PrefixTemplate). + PutLedgerNameFixed(ledgerName). + PutString(templateName). + Consume() +} + +// CounterKey builds the per-ledger event counter key. +// +// [0x02][ledgerName padded 64B][counterID] +func CounterKey(kb *dal.KeyBuilder, ledgerName string, counterID byte) []byte { + return kb.Reset(). + PutByte(PrefixCounter). + PutLedgerNameFixed(ledgerName). + PutByte(counterID). + Consume() +} diff --git a/internal/storage/usagestore/metrics.go b/internal/storage/usagestore/metrics.go new file mode 100644 index 0000000000..a484f892b5 --- /dev/null +++ b/internal/storage/usagestore/metrics.go @@ -0,0 +1,66 @@ +package usagestore + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +// RegisterMetrics registers Pebble-internal metrics for the usage store. +// Mirrors readstore.Store.RegisterMetrics with a "usagestore." namespace so +// operators can see LSM state, cache hit rates, and memtable pressure per +// secondary store. +func (s *Store) RegisterMetrics(m metric.Meter) (metric.Registration, error) { + levelBytes, err := m.Int64ObservableGauge( + "usagestore.level.bytes", + metric.WithDescription("Total bytes in each Pebble level"), + metric.WithUnit("By"), + ) + if err != nil { + return nil, fmt.Errorf("creating usagestore.level.bytes gauge: %w", err) + } + + memtableBytes, err := m.Int64ObservableGauge( + "usagestore.memtable.bytes", + metric.WithDescription("Current memtable size in bytes"), + metric.WithUnit("By"), + ) + if err != nil { + return nil, fmt.Errorf("creating usagestore.memtable.bytes gauge: %w", err) + } + + cacheHits, err := m.Int64ObservableGauge( + "usagestore.cache.hits", + metric.WithDescription("Block cache hits"), + metric.WithUnit("{hits}"), + ) + if err != nil { + return nil, fmt.Errorf("creating usagestore.cache.hits gauge: %w", err) + } + + cacheMisses, err := m.Int64ObservableGauge( + "usagestore.cache.misses", + metric.WithDescription("Block cache misses"), + metric.WithUnit("{misses}"), + ) + if err != nil { + return nil, fmt.Errorf("creating usagestore.cache.misses gauge: %w", err) + } + + return m.RegisterCallback(func(_ context.Context, o metric.Observer) error { + metrics := s.db.Metrics() + + for i, level := range metrics.Levels { + o.ObserveInt64(levelBytes, level.TablesSize, + metric.WithAttributes(attribute.Int("level", i))) + } + + o.ObserveInt64(memtableBytes, int64(metrics.MemTable.Size)) + o.ObserveInt64(cacheHits, metrics.BlockCache.Hits) + o.ObserveInt64(cacheMisses, metrics.BlockCache.Misses) + + return nil + }, levelBytes, memtableBytes, cacheHits, cacheMisses) +} diff --git a/internal/storage/usagestore/store.go b/internal/storage/usagestore/store.go new file mode 100644 index 0000000000..cb57827ac3 --- /dev/null +++ b/internal/storage/usagestore/store.go @@ -0,0 +1,258 @@ +package usagestore + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/cockroachdb/pebble/v2" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/dal" + "github.com/formancehq/ledger/v3/internal/storage/pebblecfg" +) + +// Config is the Pebble configuration for the usage store. +// Reuses the same tunables as the primary store (pebblecfg.Config). +type Config = pebblecfg.Config + +// DefaultConfig returns the default Pebble configuration for the usage store. +// Sized smaller than the read index: the usage store holds O(ledgers × templates) +// entries plus a handful of per-ledger counters, so it never grows large. +func DefaultConfig() Config { + return Config{ + MemTableSize: 16 << 20, // 16MB + MemTableStopWritesThreshold: 4, + L0CompactionThreshold: 4, + L0StopWritesThreshold: 12, + LBaseMaxBytes: 128 << 20, // 128MB + CacheSize: 16 << 20, // 16MB + TargetFileSize: 16 << 20, // 16MB + BytesPerSync: 512 << 10, // 512KB + MaxConcurrentCompactions: 1, + Compression: pebblecfg.DefaultLevelCompression(), + } +} + +// Store wraps a Pebble database for the usagebuilder's projections. +// It is a peer to readstore.Store — a distinct physical secondary store, +// so a corruption of one cannot touch the other and each subsystem's +// rebuild story is decoupled (drop the directory + restart). +type Store struct { + db *pebble.DB + logger logging.Logger + dir string +} + +// New opens or creates a Pebble database at the given directory. +func New(dir string, logger logging.Logger, cfg Config) (*Store, error) { + if err := os.MkdirAll(dir, 0o750); err != nil { + return nil, fmt.Errorf("creating usage store directory: %w", err) + } + + dbPath := filepath.Join(dir, "usagedb") + + var fileSize int64 + if info, _ := os.Stat(dbPath); info != nil { + fileSize = info.Size() + } + + logger.WithFields(map[string]any{ + "path": dbPath, + "fileSize": fileSize, + }).Infof("Opening Pebble usage store") + + openStart := time.Now() + + cache := pebble.NewCache(cfg.CacheSize) + defer cache.Unref() + + opts := &pebble.Options{ + Logger: dal.NewPebbleLogger(logger), + FormatMajorVersion: pebble.FormatNewest, + Comparer: UsageStoreComparer, + // The usage store is a derived projection rebuilt from the audit log. + // WAL disabled — on crash the usagebuilder simply replays from its + // last progress cursor. + DisableWAL: true, + MemTableSize: cfg.MemTableSize, + MemTableStopWritesThreshold: cfg.MemTableStopWritesThreshold, + L0CompactionThreshold: cfg.L0CompactionThreshold, + L0StopWritesThreshold: cfg.L0StopWritesThreshold, + LBaseMaxBytes: cfg.LBaseMaxBytes, + BytesPerSync: cfg.BytesPerSync, + CompactionConcurrencyRange: func() (int, int) { + n := cfg.MaxConcurrentCompactions + + return n, n + }, + Cache: cache, + TargetFileSizes: cfg.BuildTargetFileSizes(), + Levels: cfg.BuildLevels(), + } + + db, err := pebble.Open(dbPath, opts) + if err != nil { + return nil, fmt.Errorf("opening Pebble usage store: %w", err) + } + + m := db.Metrics() + logger.WithFields(map[string]any{ + "duration": time.Since(openStart).String(), + "l0FileCount": m.Levels[0].TablesCount, + "l0Size": m.Levels[0].TablesSize, + "memTableCount": m.MemTable.Count, + "memTableSize": m.MemTable.Size, + "totalLevelsSize": m.DiskSpaceUsage(), + }).Infof("Pebble usage store opened — LSM state") + + return &Store{ + db: db, + logger: logger.WithFields(map[string]any{"cmp": "usage-store"}), + dir: dir, + }, nil +} + +// OpenReadOnly opens a Pebble usage store at dirPath in read-only mode. +// The caller must call Close() when done. +func OpenReadOnly(dirPath string, logger logging.Logger) (*Store, error) { + db, err := pebble.Open(dirPath, &pebble.Options{ + Logger: dal.NewPebbleLogger(logger), + Comparer: UsageStoreComparer, + ReadOnly: true, + }) + if err != nil { + return nil, fmt.Errorf("opening read-only Pebble usage store at %s: %w", dirPath, err) + } + + return &Store{ + db: db, + logger: logger.WithFields(map[string]any{"cmp": "usage-store-readonly"}), + dir: dirPath, + }, nil +} + +// CreateCheckpoint creates a Pebble checkpoint of the usage store at destDir. +func (s *Store) CreateCheckpoint(destDir string) error { + return s.db.Checkpoint(destDir) +} + +// Close closes the underlying Pebble database. +func (s *Store) Close() error { + return s.db.Close() +} + +// DB returns the underlying Pebble database for creating batches. +func (s *Store) DB() *pebble.DB { + return s.db +} + +// NewBatch creates a dal.WriteSession backed by the usage store's Pebble DB. +func (s *Store) NewBatch() *dal.WriteSession { + return dal.NewWriteSessionFromDB(s.db) +} + +// Path returns the directory of the usage store. +func (s *Store) Path() string { + return s.dir +} + +// ReadProgress returns the last log sequence consumed by the usagebuilder. +// Returns 0 if no progress has been recorded. +func (s *Store) ReadProgress() (uint64, error) { + v, closer, err := s.db.Get(ProgressKey()) + if err != nil { + if errors.Is(err, pebble.ErrNotFound) { + return 0, nil + } + + return 0, fmt.Errorf("reading usage progress: %w", err) + } + + defer func() { _ = closer.Close() }() + + if len(v) != 8 { + return 0, fmt.Errorf("corrupt usage progress value: expected 8 bytes, got %d", len(v)) + } + + return binary.BigEndian.Uint64(v), nil +} + +// WriteProgress stores the last log sequence consumed by the usagebuilder. +func (s *Store) WriteProgress(batch *dal.WriteSession, sequence uint64) error { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], sequence) + + return batch.SetBytes(ProgressKey(), buf[:]) +} + +// GetTemplateUsage reads the current usage record for (ledger, template). +// Returns (nil, nil) if no entry exists. +func (s *Store) GetTemplateUsage(ledgerName, templateName string) (*commonpb.TemplateUsage, error) { + kb := dal.NewKeyBuilder() + key := TemplateUsageKey(kb, ledgerName, templateName) + + v, closer, err := s.db.Get(key) + if err != nil { + if errors.Is(err, pebble.ErrNotFound) { + return nil, nil + } + + return nil, fmt.Errorf("reading template usage: %w", err) + } + + defer func() { _ = closer.Close() }() + + usage := &commonpb.TemplateUsage{} + if err := usage.UnmarshalVT(v); err != nil { + return nil, fmt.Errorf("unmarshaling template usage: %w", err) + } + + return usage, nil +} + +// PutTemplateUsage persists a template usage record into the pending batch. +func (s *Store) PutTemplateUsage(batch *dal.WriteSession, ledgerName, templateName string, usage *commonpb.TemplateUsage) error { + key := TemplateUsageKey(batch.KeyBuilder, ledgerName, templateName) + + return batch.SetProto(key, usage) +} + +// GetCounter reads the current value of a per-ledger event counter. +// Returns 0 if no entry exists. +func (s *Store) GetCounter(ledgerName string, counterID byte) (uint64, error) { + kb := dal.NewKeyBuilder() + key := CounterKey(kb, ledgerName, counterID) + + v, closer, err := s.db.Get(key) + if err != nil { + if errors.Is(err, pebble.ErrNotFound) { + return 0, nil + } + + return 0, fmt.Errorf("reading counter %#x for ledger %q: %w", counterID, ledgerName, err) + } + + defer func() { _ = closer.Close() }() + + if len(v) != 8 { + return 0, fmt.Errorf("corrupt counter value: expected 8 bytes, got %d", len(v)) + } + + return binary.BigEndian.Uint64(v), nil +} + +// PutCounter persists a per-ledger event counter value into the pending batch. +func (s *Store) PutCounter(batch *dal.WriteSession, ledgerName string, counterID byte, value uint64) error { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], value) + + key := CounterKey(batch.KeyBuilder, ledgerName, counterID) + + return batch.SetBytes(key, buf[:]) +} diff --git a/internal/storage/usagestore/store_test.go b/internal/storage/usagestore/store_test.go new file mode 100644 index 0000000000..b53d5a98ad --- /dev/null +++ b/internal/storage/usagestore/store_test.go @@ -0,0 +1,155 @@ +package usagestore_test + +import ( + "context" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +func newTestStore(t *testing.T) *usagestore.Store { + t.Helper() + + s, err := usagestore.New(t.TempDir(), discardLogger{}, usagestore.DefaultConfig()) + require.NoError(t, err) + + t.Cleanup(func() { _ = s.Close() }) + + return s +} + +func TestStore_ProgressRoundTrip(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + seq, err := s.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(0), seq, "fresh store must report cursor 0") + + batch := s.NewBatch() + require.NoError(t, s.WriteProgress(batch, 42)) + require.NoError(t, batch.Commit()) + + seq, err = s.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(42), seq) +} + +func TestStore_CounterRoundTrip(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + v, err := s.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(0), v, "missing counter must return 0, not error") + + batch := s.NewBatch() + require.NoError(t, s.PutCounter(batch, "l1", usagestore.CounterPosting, 123)) + require.NoError(t, s.PutCounter(batch, "l1", usagestore.CounterRevert, 4)) + require.NoError(t, s.PutCounter(batch, "l2", usagestore.CounterPosting, 999)) + require.NoError(t, batch.Commit()) + + posting, err := s.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(123), posting) + + revert, err := s.GetCounter("l1", usagestore.CounterRevert) + require.NoError(t, err) + assert.Equal(t, uint64(4), revert) + + other, err := s.GetCounter("l2", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(999), other, "counters must be per-ledger scoped") +} + +func TestStore_TemplateUsageRoundTrip(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + usage, err := s.GetTemplateUsage("l1", "missing") + require.NoError(t, err) + assert.Nil(t, usage, "missing template must return (nil, nil)") + + want := &commonpb.TemplateUsage{ + Count: 7, + LastUsed: &commonpb.Timestamp{Data: 1_700_000_000_000_000_000}, + } + + batch := s.NewBatch() + require.NoError(t, s.PutTemplateUsage(batch, "l1", "payout", want)) + require.NoError(t, batch.Commit()) + + got, err := s.GetTemplateUsage("l1", "payout") + require.NoError(t, err) + require.NotNil(t, got) + assert.Equal(t, want.GetCount(), got.GetCount()) + assert.Equal(t, want.GetLastUsed().GetData(), got.GetLastUsed().GetData()) +} + +func TestStore_DeleteLedgerCascade(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + // Seed both scopes for two ledgers. + batch := s.NewBatch() + require.NoError(t, s.PutCounter(batch, "l1", usagestore.CounterPosting, 10)) + require.NoError(t, s.PutTemplateUsage(batch, "l1", "t1", &commonpb.TemplateUsage{Count: 3})) + require.NoError(t, s.PutCounter(batch, "l2", usagestore.CounterPosting, 20)) + require.NoError(t, s.PutTemplateUsage(batch, "l2", "t2", &commonpb.TemplateUsage{Count: 5})) + require.NoError(t, batch.Commit()) + + // Drop l1 only. + batch = s.NewBatch() + require.NoError(t, usagestore.DeleteLedger(batch, "l1")) + require.NoError(t, batch.Commit()) + + // l1 is gone. + v, err := s.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(0), v) + + tu, err := s.GetTemplateUsage("l1", "t1") + require.NoError(t, err) + assert.Nil(t, tu) + + // l2 survives. + v, err = s.GetCounter("l2", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(20), v) + + tu, err = s.GetTemplateUsage("l2", "t2") + require.NoError(t, err) + require.NotNil(t, tu) + assert.Equal(t, uint64(5), tu.GetCount()) +} + +// discardLogger mirrors readstore's test helper (see index_version_test.go). +// Not exported so each secondary store test package owns its own. +type discardLogger struct{} + +var _ logging.Logger = discardLogger{} + +func (discardLogger) Tracef(string, ...any) {} +func (discardLogger) Debugf(string, ...any) {} +func (discardLogger) Infof(string, ...any) {} +func (discardLogger) Errorf(string, ...any) {} +func (discardLogger) Trace(...any) {} +func (discardLogger) Debug(...any) {} +func (discardLogger) Info(...any) {} +func (discardLogger) Error(...any) {} +func (l discardLogger) WithFields(map[string]any) logging.Logger { return l } +func (l discardLogger) WithField(string, any) logging.Logger { return l } +func (l discardLogger) WithContext(context.Context) logging.Logger { return l } +func (discardLogger) Writer() io.Writer { return io.Discard } +func (discardLogger) Enabled(logging.Level) bool { return false } diff --git a/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet b/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet index 3367c0d91c..0633c83aa4 100644 --- a/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet +++ b/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet @@ -70,6 +70,14 @@ lag: 'audit_index.lag', }, + // usage.builder — internal/application/usagebuilder/builder.go + usage_builder:: { + last_processed_audit_sequence: 'usage.builder.last_processed_audit_sequence', + pebble_last_audit_sequence: 'usage.builder.pebble_last_audit_sequence', + lag: 'usage.builder.lag', + entries_processed_total: 'usage.builder.entries_processed_total', + }, + // numscript — internal/domain/processing/numscript/cache.go numscript:: { cache_size: 'numscript.cache.size', @@ -156,6 +164,14 @@ cache_misses: 'readindex.cache.misses', }, + // usagestore — internal/storage/usagestore/metrics.go + usagestore:: { + level_bytes: 'usagestore.level.bytes', + memtable_bytes: 'usagestore.memtable.bytes', + cache_hits: 'usagestore.cache.hits', + cache_misses: 'usagestore.cache.misses', + }, + // health — internal/infra/health/healthcheck.go health:: { disk_poll_failures: 'health.disk.poll.failures', diff --git a/misc/proto/bucket.proto b/misc/proto/bucket.proto index 1db85ce39d..62f56d93c6 100644 --- a/misc/proto/bucket.proto +++ b/misc/proto/bucket.proto @@ -82,6 +82,10 @@ service BucketService { rpc GetNumscript(GetNumscriptRequest) returns (common.NumscriptInfo); // ListNumscripts streams all numscripts (latest version of each) rpc ListNumscripts(ListNumscriptsRequest) returns (stream common.NumscriptInfo); + // GetTemplateUsage returns per-template invocation usage populated by the + // usagebuilder subsystem. Eventually consistent — may lag the live FSM + // by up to one usagebuilder tick interval. + rpc GetTemplateUsage(GetTemplateUsageRequest) returns (common.TemplateUsage); // InspectIndex scans a metadata index and returns distinct values, facets, or a summary rpc InspectIndex(InspectIndexRequest) returns (InspectIndexResponse); // Barrier proposes a no-op through Raft consensus. When it returns, all @@ -433,6 +437,15 @@ message ListNumscriptsRequest { common.ListOptions options = 2; } +// GetTemplateUsageRequest retrieves the invocation counter + last-used +// timestamp for a Numscript template. The counter is materialised +// asynchronously by the usagebuilder from the audit chain — expect up to +// one tick interval of lag behind the live FSM. +message GetTemplateUsageRequest { + string ledger = 1; + string name = 2; +} + // ScriptReference references a numscript from the library by name and optional version. message ScriptReference { string name = 1; diff --git a/misc/proto/common.proto b/misc/proto/common.proto index 107b34bab9..779c9b0f4a 100644 --- a/misc/proto/common.proto +++ b/misc/proto/common.proto @@ -503,6 +503,14 @@ message DeletedNumscriptLog { string ledger = 2; } +// TemplateUsage tracks per-template invocation usage. Persisted as a +// projection of the audit log by the usagebuilder subsystem — NOT part of +// the FSM authoritative state. Rebuildable from cursor=0 on demand. +message TemplateUsage { + fixed64 count = 1; + Timestamp last_used = 2; +} + // SetQueryCheckpointScheduleLog records a query checkpoint schedule change. message SetQueryCheckpointScheduleLog { string cron = 1; diff --git a/misc/proto/raft_cmd.proto b/misc/proto/raft_cmd.proto index ca89933ad4..6ccc47cbae 100644 --- a/misc/proto/raft_cmd.proto +++ b/misc/proto/raft_cmd.proto @@ -668,12 +668,8 @@ message LedgerBoundaries { fixed64 next_log_id = 2; fixed64 volume_count = 3; fixed64 metadata_count = 4; - fixed64 reference_count = 5; - fixed64 posting_count = 6; fixed64 ephemeral_evicted_count = 7; fixed64 transient_used_count = 8; - fixed64 revert_count = 9; - fixed64 numscript_execution_count = 10; } message VolumePair { diff --git a/openapi.yml b/openapi.yml index 4f7619c288..d935f0ab40 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1419,6 +1419,49 @@ paths: '405': $ref: '#/components/responses/MethodNotAllowed' + /v3/{ledgerName}/numscripts/{name}/usage: + get: + summary: Get template usage + description: | + Returns the invocation counter and last-used timestamp for a Numscript template. Values are + materialised asynchronously by the `usagebuilder` subsystem from the FSM audit chain — expect + up to one usagebuilder tick interval (~100 ms) of lag behind the live FSM. A never-invoked + template returns a zero-valued response (not 404), so clients handle "never used" uniformly. + + On existing ledgers whose audit chain has been partially archived to cold storage, only + invocations still reachable in Pebble are counted. Use `ledgerctl store rebuild-usage` to + replay from the reachable start. + operationId: getNumscriptUsage + tags: + - Numscript Library + parameters: + - $ref: '#/components/parameters/LedgerName' + - name: name + in: path + required: true + schema: + type: string + description: Name of the numscript template + responses: + '200': + description: Template usage retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetTemplateUsageResponse' + '400': + $ref: '#/components/responses/BadRequest' + '503': + $ref: '#/components/responses/ServiceUnavailable' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '405': + $ref: '#/components/responses/MethodNotAllowed' + /v3/{ledgerName}/bulk: post: summary: Bulk operations @@ -3485,6 +3528,33 @@ components: description: List of all numscripts nullable: true + TemplateUsage: + type: object + required: + - count + properties: + count: + type: integer + format: int64 + minimum: 0 + description: | + Number of times the template has been invoked. `0` means never invoked (or the + usagebuilder has not yet caught up to any invocation on this replica). + lastUsed: + type: string + format: date-time + nullable: true + description: | + Timestamp of the most recent invocation (ISO 8601). Absent when `count` is 0. + + GetTemplateUsageResponse: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/TemplateUsage' + ChartOfAccounts: type: object description: A suggested chart of accounts based on account analysis From 051b93513052e51ade8da27ae5ae51991e2b4ae3 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Wed, 1 Jul 2026 22:48:39 +0200 Subject: [PATCH 02/35] feat(EN-1420): extend usagebuilder with ephemeral + transient counters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrects an earlier misclassification: EphemeralEvictedCount and TransientUsedCount are event counts, not attribute-derived. They live on the audit chain via LedgerLog.PurgedVolumes (per-log) and AppliedProposal.TransientVolumes (per-proposal) — the checker already verifies both projections under compareExclusionProjections. Migrates both counters out of LedgerBoundaries into the usagebuilder: - New usagestore counter IDs: CounterEphemeralEvicted (0x05), CounterTransientUsed (0x06). - process_audit.go: countsFromLog now returns postings + purged in one pass (PurgedVolumes lives on LedgerLog directly, independent of payload variant). TransientVolumes is fetched once per audit entry via query.ReadAppliedProposal and dispatched per ledger. - write_set_counters.go: ephemeralEvicted / transientUsed tracking removed, updateBoundaryCounters now only maintains volume_count and metadata_count. purgedVolumes / transientVolumes params retained for the volume_count adjustment (ephemeral / transient volumes must not count as live entries). - raft_cmd.proto: LedgerBoundaries reduced to 4 fields (next_transaction_id, next_log_id, volume_count, metadata_count). - controller_default.go: LedgerStats reads both counters from the usagestore side-store. Also adds a Pebble snapshot wrapper (usagestore.Snapshot) and switches GetLedgerStats to route all six event-counter reads through it. Without this, a concurrent usagebuilder commit could land partial state between consecutive Gets and produce an inconsistent LedgerStats response. Writes stay batched (single Pebble batch per commit) so the cursor and every counter delta move atomically at each processed audit index. EN-1422 shrinks accordingly: now only VolumeCount and MetadataCount remain on LedgerBoundaries pending decision. --- .../application/ctrl/controller_default.go | 38 +++++--- .../application/usagebuilder/process_audit.go | 92 ++++++++++++++----- internal/infra/state/write_set_counters.go | 35 +++---- internal/proto/raftcmdpb/raft_cmd.pb.go | 36 ++------ .../proto/raftcmdpb/raft_cmd_reader.pb.go | 10 -- .../proto/raftcmdpb/raft_cmd_vtproto.pb.go | 46 ---------- internal/storage/usagestore/keys.go | 10 +- internal/storage/usagestore/snapshot.go | 82 +++++++++++++++++ misc/proto/raft_cmd.proto | 2 - 9 files changed, 202 insertions(+), 149 deletions(-) create mode 100644 internal/storage/usagestore/snapshot.go diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index 1a74c445fd..f9f1be9f09 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -502,12 +502,12 @@ func (ctrl *DefaultController) GetAccount(ctx context.Context, ledgerName string } // GetLedgerStats returns aggregate statistics for a ledger. -// TransactionCount, LogCount, VolumeCount, MetadataCount, EphemeralEvictedCount -// and TransientUsedCount come from the LedgerBoundaries attribute. The -// event-count fields (PostingCount, RevertCount, NumscriptExecutionCount, -// ReferenceCount) are derived from the audit chain by the usagebuilder and -// read from the usagestore side-store — expect up to one usagebuilder tick -// interval of lag behind the live FSM (see EN-1420). +// TransactionCount, LogCount, VolumeCount and MetadataCount come from the +// LedgerBoundaries attribute. Every event-count field (PostingCount, +// RevertCount, NumscriptExecutionCount, ReferenceCount, EphemeralEvictedCount, +// TransientUsedCount) is derived from the audit chain by the usagebuilder +// and read from the usagestore side-store — expect up to one usagebuilder +// tick interval of lag behind the live FSM (see EN-1420). func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName string) (*commonpb.LedgerStats, error) { handle, err := ctrl.store.NewReadHandle() if err != nil { @@ -537,32 +537,42 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st stats.VolumeCount = boundaries.GetVolumeCount() stats.MetadataCount = boundaries.GetMetadataCount() - stats.EphemeralEvictedCount = boundaries.GetEphemeralEvictedCount() - stats.TransientUsedCount = boundaries.GetTransientUsedCount() if nextLogID := boundaries.GetNextLogId(); nextLogID > 0 { stats.LogCount = nextLogID - 1 } } - // Event-count fields from the usagebuilder side-store. Missing keys read - // as 0 (fresh ledger, or usagebuilder has not caught up yet). - if stats.PostingCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterPosting); err != nil { + // Event-count fields from the usagebuilder side-store. All six reads go + // against a single Pebble snapshot so a concurrent usagebuilder commit + // cannot land a partial view between them. Missing keys read as 0. + usageSnap := ctrl.usageStore.NewSnapshot() + defer func() { _ = usageSnap.Close() }() + + if stats.PostingCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterPosting); err != nil { return nil, fmt.Errorf("reading posting counter: %w", err) } - if stats.RevertCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterRevert); err != nil { + if stats.RevertCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterRevert); err != nil { return nil, fmt.Errorf("reading revert counter: %w", err) } - if stats.NumscriptExecutionCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterNumscriptExecution); err != nil { + if stats.NumscriptExecutionCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterNumscriptExecution); err != nil { return nil, fmt.Errorf("reading numscript execution counter: %w", err) } - if stats.ReferenceCount, err = ctrl.usageStore.GetCounter(ledgerName, usagestore.CounterReference); err != nil { + if stats.ReferenceCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterReference); err != nil { return nil, fmt.Errorf("reading reference counter: %w", err) } + if stats.EphemeralEvictedCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterEphemeralEvicted); err != nil { + return nil, fmt.Errorf("reading ephemeral evicted counter: %w", err) + } + + if stats.TransientUsedCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterTransientUsed); err != nil { + return nil, fmt.Errorf("reading transient used counter: %w", err) + } + return &stats, nil } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 914bb56ae9..f8b1777ad0 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -158,6 +158,23 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli continue } + // TransientVolumes live on the AppliedProposal projection (keyed by + // audit sequence — 1:1 with AuditEntry). Batch-scoped, not per-log: + // transientness depends on the WriteSet's final aggregate state, + // so it cannot be split per item. + proposal, err := query.ReadAppliedProposal(ctx, handle, entry.GetSequence()) + if err != nil { + return cursor, fmt.Errorf("reading applied proposal for seq %d: %w", entry.GetSequence(), err) + } + + if proposal != nil { + for ledger, tvList := range proposal.GetTransientVolumes() { + if n := len(tvList.GetVolumes()); n > 0 { + state.addCounter(ledger, usagestore.CounterTransientUsed, counterDelta(n)) + } + } + } + items, err := query.ReadAuditItems(ctx, handle, entry.GetSequence()) if err != nil { return cursor, fmt.Errorf("reading audit items for seq %d: %w", entry.GetSequence(), err) @@ -267,7 +284,7 @@ func (b *Builder) dispatchOrder( } // dispatchCreateTransaction increments posting, reference, numscript-exec, -// and template usage counters for a create-tx order. +// ephemeral-evicted and template usage counters for a create-tx order. func (b *Builder) dispatchCreateTransaction( ctx context.Context, handle dal.PebbleGetter, @@ -276,15 +293,20 @@ func (b *Builder) dispatchCreateTransaction( logSeq uint64, state *batchState, ) error { - // Resolved posting count lives on the log — the order carries raw - // postings only for the non-scripted path. - postings, err := b.postingsFromLog(ctx, handle, logSeq) + // Resolved posting count and purged-volume count live on the log — the + // order carries raw postings only for the non-scripted path, and never + // carries purge info. + counts, err := b.countsFromLog(ctx, handle, logSeq) if err != nil { return err } - if postings > 0 { - state.addCounter(ledger, usagestore.CounterPosting, counterDelta(postings)) + if counts.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) + } + + if counts.purged > 0 { + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) } if order.GetReference() != "" { @@ -305,8 +327,9 @@ func (b *Builder) dispatchCreateTransaction( return nil } -// dispatchRevertTransaction increments revert and posting counters for a -// revert-tx order. The resolved reverse-postings live on the log. +// dispatchRevertTransaction increments revert, posting and ephemeral-evicted +// counters for a revert-tx order. The resolved reverse-postings + purged +// volumes live on the produced log. func (b *Builder) dispatchRevertTransaction( ctx context.Context, handle dal.PebbleGetter, @@ -316,50 +339,71 @@ func (b *Builder) dispatchRevertTransaction( ) error { state.addCounter(ledger, usagestore.CounterRevert, 1) - postings, err := b.postingsFromLog(ctx, handle, logSeq) + counts, err := b.countsFromLog(ctx, handle, logSeq) if err != nil { return err } - if postings > 0 { - state.addCounter(ledger, usagestore.CounterPosting, counterDelta(postings)) + if counts.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) + } + + if counts.purged > 0 { + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) } return nil } -// postingsFromLog fetches the log at logSeq and returns the resolved posting -// count. Handles both CreatedTransaction and RevertedTransaction payloads. -// Returns 0 if the log does not exist or carries no transaction (e.g. -// metadata-only logs). -func (b *Builder) postingsFromLog(ctx context.Context, handle dal.PebbleGetter, logSeq uint64) (int, error) { +// logCounts is the tuple of per-log counter deltas extracted from a single +// LedgerLog payload. +type logCounts struct { + postings int + purged int +} + +// countsFromLog fetches the log at logSeq and returns the resolved posting +// count plus the number of ephemerally-purged volumes attached to this +// specific log. Both are zero if the log does not exist or carries no +// relevant payload. +func (b *Builder) countsFromLog(ctx context.Context, handle dal.PebbleGetter, logSeq uint64) (logCounts, error) { log, err := query.ReadLogBySequence(ctx, handle, logSeq) if err != nil { - return 0, fmt.Errorf("reading log at seq %d: %w", logSeq, err) + return logCounts{}, fmt.Errorf("reading log at seq %d: %w", logSeq, err) } if log == nil { - return 0, nil + return logCounts{}, nil } apply, ok := log.GetPayload().GetType().(*commonpb.LogPayload_Apply) if !ok || apply.Apply == nil { - return 0, nil + return logCounts{}, nil } ledgerLog := apply.Apply.GetLog() - if ledgerLog == nil || ledgerLog.GetData() == nil { - return 0, nil + if ledgerLog == nil { + return logCounts{}, nil + } + + // PurgedVolumes lives on LedgerLog directly (not on the payload variant), + // so we can extract it independently of the payload type. + result := logCounts{ + purged: len(ledgerLog.GetPurgedVolumes()), + } + + if ledgerLog.GetData() == nil { + return result, nil } switch p := ledgerLog.GetData().GetPayload().(type) { case *commonpb.LedgerLogPayload_CreatedTransaction: - return len(p.CreatedTransaction.GetTransaction().GetPostings()), nil + result.postings = len(p.CreatedTransaction.GetTransaction().GetPostings()) case *commonpb.LedgerLogPayload_RevertedTransaction: - return len(p.RevertedTransaction.GetRevertTransaction().GetPostings()), nil + result.postings = len(p.RevertedTransaction.GetRevertTransaction().GetPostings()) } - return 0, nil + return result, nil } // commitBatch applies the accumulated counter / template deltas to the diff --git a/internal/infra/state/write_set_counters.go b/internal/infra/state/write_set_counters.go index fa9f0347cc..4a74e59af0 100644 --- a/internal/infra/state/write_set_counters.go +++ b/internal/infra/state/write_set_counters.go @@ -77,10 +77,17 @@ func countVolumeDeltas(updates []attributes.Update[domain.VolumeKey, *raftcmdpb. // updateBoundaryCounters computes attribute key deltas and updates LedgerBoundaries // for each affected ledger. Must be called before Derived.Boundaries.Merge(). // -// Only the attribute-derived counters (volume_count, metadata_count, -// ephemeral_evicted_count, transient_used_count) live here — reference_count -// moved to the usagebuilder (see EN-1420) which derives it from the audit -// chain instead. The 4 remaining are covered by EN-1422. +// Only the two attribute-derived counters (volume_count, metadata_count) live +// here now — reference_count, posting_count, revert_count, numscript_execution_count, +// ephemeral_evicted_count and transient_used_count all migrated to the +// usagebuilder (EN-1420) which derives them from the audit chain instead. +// The two remaining counters are covered by EN-1422. +// +// purgedVolumes and transientVolumes are still consumed here — not to feed +// their own counter (that lives in the usagebuilder), but to correctly +// subtract them from volume_count: an ephemeral volume that never survives +// commit and a transient volume that never persists must not count as a live +// volume-store entry. func (b *WriteSet) updateBoundaryCounters( volumeUpdates []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], purgedVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], @@ -90,21 +97,13 @@ func (b *WriteSet) updateBoundaryCounters( ) { volumeDeltas := countVolumeDeltas(volumeUpdates) - // Per-ledger ephemeral/transient counters (monotonic). - ephemeralEvicted := make(map[string]uint64) - transientUsed := make(map[string]uint64) - // Ephemeral volumes are purged after commit — subtract from volume count. for i := range purgedVolumes { - ledger := purgedVolumes[i].Key.LedgerName - volumeDeltas[ledger]-- - ephemeralEvicted[ledger]++ + volumeDeltas[purgedVolumes[i].Key.LedgerName]-- } // Transient volumes are never persisted — subtract from volume count. for i := range transientVolumes { - ledger := transientVolumes[i].Key.LedgerName - volumeDeltas[ledger]-- - transientUsed[ledger]++ + volumeDeltas[transientVolumes[i].Key.LedgerName]-- } metadataDeltas := countKeyDeltas(metadataUpdates, metadataDeletions, func(k domain.MetadataKey) string { return k.LedgerName }) @@ -117,12 +116,6 @@ func (b *WriteSet) updateBoundaryCounters( for ledger := range metadataDeltas { affected[ledger] = struct{}{} } - for ledger := range ephemeralEvicted { - affected[ledger] = struct{}{} - } - for ledger := range transientUsed { - affected[ledger] = struct{}{} - } for ledgerName := range affected { boundariesReader, err := b.Boundaries().Get(domain.LedgerKey{Name: ledgerName}) @@ -133,8 +126,6 @@ func (b *WriteSet) updateBoundaryCounters( boundaries := boundariesReader.Mutate() boundaries.VolumeCount = applyDelta(boundaries.GetVolumeCount(), volumeDeltas[ledgerName]) boundaries.MetadataCount = applyDelta(boundaries.GetMetadataCount(), metadataDeltas[ledgerName]) - boundaries.EphemeralEvictedCount += ephemeralEvicted[ledgerName] - boundaries.TransientUsedCount += transientUsed[ledgerName] b.Boundaries().Put(domain.LedgerKey{Name: ledgerName}, boundaries) } } diff --git a/internal/proto/raftcmdpb/raft_cmd.pb.go b/internal/proto/raftcmdpb/raft_cmd.pb.go index 418976a4b4..1c4bf91b52 100644 --- a/internal/proto/raftcmdpb/raft_cmd.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd.pb.go @@ -4882,15 +4882,13 @@ func (*CreatedLogOrReference_CreatedLog) isCreatedLogOrReference_Type() {} func (*CreatedLogOrReference_ReferenceSequence) isCreatedLogOrReference_Type() {} type LedgerBoundaries struct { - state protoimpl.MessageState `protogen:"open.v1"` - NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` - NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` - VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` - MetadataCount uint64 `protobuf:"fixed64,4,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` - EphemeralEvictedCount uint64 `protobuf:"fixed64,7,opt,name=ephemeral_evicted_count,json=ephemeralEvictedCount,proto3" json:"ephemeral_evicted_count,omitempty"` - TransientUsedCount uint64 `protobuf:"fixed64,8,opt,name=transient_used_count,json=transientUsedCount,proto3" json:"transient_used_count,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` + NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` + VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` + MetadataCount uint64 `protobuf:"fixed64,4,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LedgerBoundaries) Reset() { @@ -4951,20 +4949,6 @@ func (x *LedgerBoundaries) GetMetadataCount() uint64 { return 0 } -func (x *LedgerBoundaries) GetEphemeralEvictedCount() uint64 { - if x != nil { - return x.EphemeralEvictedCount - } - return 0 -} - -func (x *LedgerBoundaries) GetTransientUsedCount() uint64 { - if x != nil { - return x.TransientUsedCount - } - return 0 -} - type VolumePair struct { state protoimpl.MessageState `protogen:"open.v1"` Input *commonpb.Uint256 `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` @@ -6390,14 +6374,12 @@ const file_raft_cmd_proto_rawDesc = "" + "\vcreated_log\x18\x01 \x01(\v2\v.common.LogH\x00R\n" + "createdLog\x12/\n" + "\x12reference_sequence\x18\x02 \x01(\x06H\x00R\x11referenceSequenceB\x06\n" + - "\x04type\"\x96\x02\n" + + "\x04type\"\xac\x01\n" + "\x10LedgerBoundaries\x12.\n" + "\x13next_transaction_id\x18\x01 \x01(\x06R\x11nextTransactionId\x12\x1e\n" + "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\x12!\n" + "\fvolume_count\x18\x03 \x01(\x06R\vvolumeCount\x12%\n" + - "\x0emetadata_count\x18\x04 \x01(\x06R\rmetadataCount\x126\n" + - "\x17ephemeral_evicted_count\x18\a \x01(\x06R\x15ephemeralEvictedCount\x120\n" + - "\x14transient_used_count\x18\b \x01(\x06R\x12transientUsedCount\"\\\n" + + "\x0emetadata_count\x18\x04 \x01(\x06R\rmetadataCount\"\\\n" + "\n" + "VolumePair\x12%\n" + "\x05input\x18\x01 \x01(\v2\x0f.common.Uint256R\x05input\x12'\n" + diff --git a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go index eb8e195094..c96f29ff44 100644 --- a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go @@ -5251,8 +5251,6 @@ type LedgerBoundariesReader interface { GetNextLogId() uint64 GetVolumeCount() uint64 GetMetadataCount() uint64 - GetEphemeralEvictedCount() uint64 - GetTransientUsedCount() uint64 Mutate() *LedgerBoundaries } @@ -5274,14 +5272,6 @@ func (r *ledgerBoundariesReadonly) GetMetadataCount() uint64 { return (*LedgerBoundaries)(r).GetMetadataCount() } -func (r *ledgerBoundariesReadonly) GetEphemeralEvictedCount() uint64 { - return (*LedgerBoundaries)(r).GetEphemeralEvictedCount() -} - -func (r *ledgerBoundariesReadonly) GetTransientUsedCount() uint64 { - return (*LedgerBoundaries)(r).GetTransientUsedCount() -} - func (r *ledgerBoundariesReadonly) Mutate() *LedgerBoundaries { return (*LedgerBoundaries)(r).CloneVT() } diff --git a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go index 302d41e5de..e3a44db4b6 100644 --- a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go @@ -1962,8 +1962,6 @@ func (m *LedgerBoundaries) CloneVT() *LedgerBoundaries { r.NextLogId = m.NextLogId r.VolumeCount = m.VolumeCount r.MetadataCount = m.MetadataCount - r.EphemeralEvictedCount = m.EphemeralEvictedCount - r.TransientUsedCount = m.TransientUsedCount if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -5811,12 +5809,6 @@ func (this *LedgerBoundaries) EqualVT(that *LedgerBoundaries) bool { if this.MetadataCount != that.MetadataCount { return false } - if this.EphemeralEvictedCount != that.EphemeralEvictedCount { - return false - } - if this.TransientUsedCount != that.TransientUsedCount { - return false - } return string(this.unknownFields) == string(that.unknownFields) } @@ -11066,18 +11058,6 @@ func (m *LedgerBoundaries) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.TransientUsedCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.TransientUsedCount)) - i-- - dAtA[i] = 0x41 - } - if m.EphemeralEvictedCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.EphemeralEvictedCount)) - i-- - dAtA[i] = 0x39 - } if m.MetadataCount != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.MetadataCount)) @@ -14361,12 +14341,6 @@ func (m *LedgerBoundaries) SizeVT() (n int) { if m.MetadataCount != 0 { n += 9 } - if m.EphemeralEvictedCount != 0 { - n += 9 - } - if m.TransientUsedCount != 0 { - n += 9 - } n += len(m.unknownFields) return n } @@ -25554,26 +25528,6 @@ func (m *LedgerBoundaries) UnmarshalVT(dAtA []byte) error { } m.MetadataCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 7: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field EphemeralEvictedCount", wireType) - } - m.EphemeralEvictedCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.EphemeralEvictedCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - case 8: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field TransientUsedCount", wireType) - } - m.TransientUsedCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.TransientUsedCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/internal/storage/usagestore/keys.go b/internal/storage/usagestore/keys.go index 8404b9d5b7..8e73447cd0 100644 --- a/internal/storage/usagestore/keys.go +++ b/internal/storage/usagestore/keys.go @@ -28,10 +28,12 @@ const ( // Counter IDs identify each per-ledger event counter mirrored by the // usagebuilder. Values are stable on-disk identifiers — never renumber. const ( - CounterPosting byte = 0x01 // posting_count - CounterRevert byte = 0x02 // revert_count - CounterNumscriptExecution byte = 0x03 // numscript_execution_count - CounterReference byte = 0x04 // reference_count + CounterPosting byte = 0x01 // posting_count — sum len(Transaction.Postings) per CreatedTransaction / RevertedTransaction log + CounterRevert byte = 0x02 // revert_count — count RevertTransactionOrder + MirrorRevertTransactionOrder + CounterNumscriptExecution byte = 0x03 // numscript_execution_count — count CreateTransactionOrder with Script or NumscriptReference + CounterReference byte = 0x04 // reference_count — count CreateTransactionOrder with non-empty Reference + CounterEphemeralEvicted byte = 0x05 // ephemeral_evicted_count — sum len(LedgerLog.PurgedVolumes) per log + CounterTransientUsed byte = 0x06 // transient_used_count — sum len(AppliedProposal.TransientVolumes[ledger].Volumes) per proposal ) // ProgressKey returns the full key for the usagebuilder progress entry. diff --git a/internal/storage/usagestore/snapshot.go b/internal/storage/usagestore/snapshot.go new file mode 100644 index 0000000000..5962e7bac8 --- /dev/null +++ b/internal/storage/usagestore/snapshot.go @@ -0,0 +1,82 @@ +package usagestore + +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/cockroachdb/pebble/v2" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/dal" +) + +// Snapshot is a point-in-time view of the usage store. Multiple Gets against +// the same Snapshot observe a coherent set of counter values — no +// intervening usagebuilder commit can shift one counter relative to another +// mid-read. +// +// Callers MUST call Close() to release the underlying Pebble snapshot. +type Snapshot struct { + snap *pebble.Snapshot +} + +// NewSnapshot returns a fresh point-in-time snapshot. The caller owns the +// snapshot and is responsible for closing it. +func (s *Store) NewSnapshot() *Snapshot { + return &Snapshot{snap: s.db.NewSnapshot()} +} + +// Close releases the underlying Pebble snapshot. +func (s *Snapshot) Close() error { + return s.snap.Close() +} + +// GetCounter reads the value of a per-ledger event counter from this +// snapshot. Missing keys return 0. +func (s *Snapshot) GetCounter(ledgerName string, counterID byte) (uint64, error) { + kb := dal.NewKeyBuilder() + key := CounterKey(kb, ledgerName, counterID) + + v, closer, err := s.snap.Get(key) + if err != nil { + if errors.Is(err, pebble.ErrNotFound) { + return 0, nil + } + + return 0, fmt.Errorf("reading counter %#x for ledger %q: %w", counterID, ledgerName, err) + } + + defer func() { _ = closer.Close() }() + + if len(v) != 8 { + return 0, fmt.Errorf("corrupt counter value: expected 8 bytes, got %d", len(v)) + } + + return binary.BigEndian.Uint64(v), nil +} + +// GetTemplateUsage reads a template usage record from this snapshot. +// Returns (nil, nil) when the entry does not exist. +func (s *Snapshot) GetTemplateUsage(ledgerName, templateName string) (*commonpb.TemplateUsage, error) { + kb := dal.NewKeyBuilder() + key := TemplateUsageKey(kb, ledgerName, templateName) + + v, closer, err := s.snap.Get(key) + if err != nil { + if errors.Is(err, pebble.ErrNotFound) { + return nil, nil + } + + return nil, fmt.Errorf("reading template usage: %w", err) + } + + defer func() { _ = closer.Close() }() + + usage := &commonpb.TemplateUsage{} + if err := usage.UnmarshalVT(v); err != nil { + return nil, fmt.Errorf("unmarshaling template usage: %w", err) + } + + return usage, nil +} diff --git a/misc/proto/raft_cmd.proto b/misc/proto/raft_cmd.proto index 6ccc47cbae..fdf3eaaee5 100644 --- a/misc/proto/raft_cmd.proto +++ b/misc/proto/raft_cmd.proto @@ -668,8 +668,6 @@ message LedgerBoundaries { fixed64 next_log_id = 2; fixed64 volume_count = 3; fixed64 metadata_count = 4; - fixed64 ephemeral_evicted_count = 7; - fixed64 transient_used_count = 8; } message VolumePair { From 4b7516a6a2c9fc194e3e2a907a4c485f56309da6 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 08:15:52 +0200 Subject: [PATCH 03/35] feat(EN-1420): drop MetadataCount until a sound foundation lands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admission preload no longer injects the old value of account metadata keys. Consequence: at apply time `Old.IsDefined()` returns false even for overwrites of existing keys, so `countKeyDeltas` bumps `MetadataCount` on every SavedMetadata write instead of only on newly-created keys. The counter drifted from "distinct-key cardinality" to "write-event count" without the naming reflecting it — and there is no cheap way to restore cardinality semantics without re-injecting the old value. Remove `MetadataCount` from every surface until it comes back on a sound foundation (either on-read range scan of `SubAttrMetadata`, or a fresh audit-derived event counter with an honest name): - LedgerBoundaries: field 4 removed. Field number stays gone (no reserved) because no external contract binds it. - LedgerStats: field 3 removed from the wire proto. Callers that read `metadata_count` will now see the proto3 zero-value / a decoded absent field, matching the "we don't compute this right now" reality. - `updateBoundaryCounters`: drops the metadata param pair. The dead `countKeyDeltas` helper is removed with it — nothing else used it. - HTTP DTO, controller mapping, CLI stats print, HTTP+e2e tests, and architecture doc all lose the field. Antithesis and openapi.yml did not reference it. Volume count stays — it is the last field left on `LedgerBoundaries` beyond the ID generators, and its cardinality logic still holds thanks to the volume preload path (which does inject old values). --- cmd/ledgerctl/ledgers/stats.go | 1 - docs/technical/architecture/overview.md | 9 +--- .../adapter/http/handlers_get_ledger_stats.go | 2 - .../http/handlers_get_ledger_stats_test.go | 3 -- .../application/ctrl/controller_default.go | 12 +++-- internal/infra/state/write_set.go | 5 +- internal/infra/state/write_set_counters.go | 52 ++++--------------- internal/proto/commonpb/common.pb.go | 13 +---- internal/proto/commonpb/common_reader.pb.go | 5 -- internal/proto/commonpb/common_vtproto.pb.go | 23 -------- internal/proto/raftcmdpb/raft_cmd.pb.go | 13 +---- .../proto/raftcmdpb/raft_cmd_reader.pb.go | 5 -- .../proto/raftcmdpb/raft_cmd_vtproto.pb.go | 23 -------- misc/proto/common.proto | 1 - misc/proto/raft_cmd.proto | 1 - tests/e2e/business/stats_test.go | 2 - 16 files changed, 27 insertions(+), 143 deletions(-) diff --git a/cmd/ledgerctl/ledgers/stats.go b/cmd/ledgerctl/ledgers/stats.go index aafcfa5576..0103b9770b 100644 --- a/cmd/ledgerctl/ledgers/stats.go +++ b/cmd/ledgerctl/ledgers/stats.go @@ -75,7 +75,6 @@ func runStats(cmd *cobra.Command, _ []string) error { pterm.Printf("Postings: %d\n", stats.GetPostingCount()) pterm.Printf("Logs: %d\n", stats.GetLogCount()) pterm.Printf("Volumes: %d\n", stats.GetVolumeCount()) - pterm.Printf("Metadata: %d\n", stats.GetMetadataCount()) pterm.Printf("References: %d\n", stats.GetReferenceCount()) pterm.Printf("Reverts: %d\n", stats.GetRevertCount()) pterm.Printf("Numscript execs: %d\n", stats.GetNumscriptExecutionCount()) diff --git a/docs/technical/architecture/overview.md b/docs/technical/architecture/overview.md index 4ee262fdae..239b77fa97 100644 --- a/docs/technical/architecture/overview.md +++ b/docs/technical/architecture/overview.md @@ -155,16 +155,11 @@ message LedgerBoundaries { fixed64 next_transaction_id = 1; fixed64 next_log_id = 2; fixed64 volume_count = 3; - fixed64 metadata_count = 4; - fixed64 reference_count = 5; - fixed64 posting_count = 6; - fixed64 ephemeral_evicted_count = 7; - fixed64 transient_used_count = 8; - fixed64 revert_count = 9; - fixed64 numscript_execution_count = 10; } ``` +> Event counters (postings, reverts, numscript executions, references, ephemeral evictions, transient volumes) live in the usagebuilder side-store (`internal/application/usagebuilder`, `internal/storage/usagestore`) — they are derived from the audit chain, not maintained on `LedgerBoundaries`. `metadata_count` is temporarily removed pending a sound foundation (the admission preload no longer injects old metadata values, breaking the cardinality delta logic). + > **Note:** The `State` / `LedgerState` proto messages sometimes shown in older documentation are conceptual models, not actual proto definitions. The real FSM state is spread across the `Machine` struct fields and its `StateRegistry`. ### Benefits of Single Raft diff --git a/internal/adapter/http/handlers_get_ledger_stats.go b/internal/adapter/http/handlers_get_ledger_stats.go index 0a2283e126..4a6cdbcdd5 100644 --- a/internal/adapter/http/handlers_get_ledger_stats.go +++ b/internal/adapter/http/handlers_get_ledger_stats.go @@ -10,7 +10,6 @@ import ( type ledgerStatsJSON struct { TransactionCount uint64 `json:"transactionCount"` VolumeCount uint64 `json:"volumeCount"` - MetadataCount uint64 `json:"metadataCount"` ReferenceCount uint64 `json:"referenceCount"` PostingCount uint64 `json:"postingCount"` LogCount uint64 `json:"logCount"` @@ -24,7 +23,6 @@ func toLedgerStatsJSON(stats *commonpb.LedgerStats) *ledgerStatsJSON { return &ledgerStatsJSON{ TransactionCount: stats.GetTransactionCount(), VolumeCount: stats.GetVolumeCount(), - MetadataCount: stats.GetMetadataCount(), ReferenceCount: stats.GetReferenceCount(), PostingCount: stats.GetPostingCount(), LogCount: stats.GetLogCount(), diff --git a/internal/adapter/http/handlers_get_ledger_stats_test.go b/internal/adapter/http/handlers_get_ledger_stats_test.go index 6722e5e086..0245a168b8 100644 --- a/internal/adapter/http/handlers_get_ledger_stats_test.go +++ b/internal/adapter/http/handlers_get_ledger_stats_test.go @@ -29,7 +29,6 @@ func TestHandleGetLedgerStats_Success(t *testing.T) { return &commonpb.LedgerStats{ TransactionCount: 100, VolumeCount: 42, - MetadataCount: 10, ReferenceCount: 5, PostingCount: 200, }, nil @@ -48,7 +47,6 @@ func TestHandleGetLedgerStats_Success(t *testing.T) { wrapper := decodeResponse[BaseResponse[ledgerStatsJSON]](t, w) require.Equal(t, uint64(100), wrapper.Data.TransactionCount) require.Equal(t, uint64(42), wrapper.Data.VolumeCount) - require.Equal(t, uint64(10), wrapper.Data.MetadataCount) require.Equal(t, uint64(5), wrapper.Data.ReferenceCount) require.Equal(t, uint64(200), wrapper.Data.PostingCount) } @@ -75,7 +73,6 @@ func TestHandleGetLedgerStats_EmptyLedger(t *testing.T) { wrapper := decodeResponse[BaseResponse[ledgerStatsJSON]](t, w) require.Equal(t, uint64(0), wrapper.Data.TransactionCount) require.Equal(t, uint64(0), wrapper.Data.VolumeCount) - require.Equal(t, uint64(0), wrapper.Data.MetadataCount) require.Equal(t, uint64(0), wrapper.Data.ReferenceCount) require.Equal(t, uint64(0), wrapper.Data.PostingCount) } diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index f9f1be9f09..5b9e77314f 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -502,12 +502,17 @@ func (ctrl *DefaultController) GetAccount(ctx context.Context, ledgerName string } // GetLedgerStats returns aggregate statistics for a ledger. -// TransactionCount, LogCount, VolumeCount and MetadataCount come from the -// LedgerBoundaries attribute. Every event-count field (PostingCount, -// RevertCount, NumscriptExecutionCount, ReferenceCount, EphemeralEvictedCount, +// TransactionCount, LogCount and VolumeCount come from the LedgerBoundaries +// attribute. Every event-count field (PostingCount, RevertCount, +// NumscriptExecutionCount, ReferenceCount, EphemeralEvictedCount, // TransientUsedCount) is derived from the audit chain by the usagebuilder // and read from the usagestore side-store — expect up to one usagebuilder // tick interval of lag behind the live FSM (see EN-1420). +// +// MetadataCount is intentionally not returned: the admission preload no +// longer injects old metadata values, so the FSM-side counter can no longer +// distinguish "new key" from "overwrite" and is disabled until it comes +// back on a sound foundation. func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName string) (*commonpb.LedgerStats, error) { handle, err := ctrl.store.NewReadHandle() if err != nil { @@ -536,7 +541,6 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st } stats.VolumeCount = boundaries.GetVolumeCount() - stats.MetadataCount = boundaries.GetMetadataCount() if nextLogID := boundaries.GetNextLogId(); nextLogID > 0 { stats.LogCount = nextLogID - 1 diff --git a/internal/infra/state/write_set.go b/internal/infra/state/write_set.go index 9e8db2302a..fb566a9818 100644 --- a/internal/infra/state/write_set.go +++ b/internal/infra/state/write_set.go @@ -258,7 +258,10 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create } // Update per-ledger attribute counters in boundaries before merging them. - b.updateBoundaryCounters(volumeUpdates, partResult.purged, partResult.transient, metadataUpdates, metadataDeletions) + // metadataUpdates / metadataDeletions no longer contribute to any + // boundary counter (see write_set_counters.go) — they still flow through + // to the cache flush below. + b.updateBoundaryCounters(volumeUpdates, partResult.purged, partResult.transient) boundaryUpdates, boundaryDeletions, err := b.Derived.Boundaries.Merge() if err != nil { diff --git a/internal/infra/state/write_set_counters.go b/internal/infra/state/write_set_counters.go index 4a74e59af0..3e2f5d777e 100644 --- a/internal/infra/state/write_set_counters.go +++ b/internal/infra/state/write_set_counters.go @@ -3,32 +3,9 @@ package state import ( "github.com/formancehq/ledger/v3/internal/domain" "github.com/formancehq/ledger/v3/internal/infra/attributes" - "github.com/formancehq/ledger/v3/internal/proto/commonpb" "github.com/formancehq/ledger/v3/internal/proto/raftcmdpb" ) -// countKeyDeltas counts per-ledger new keys (+1) and deletions (-1) from merge results. -// A new key is identified by Old not being defined (first time in the parent KeyStore). -func countKeyDeltas[K attributes.Key, T any]( - updates []attributes.Update[K, T], - deletions []attributes.Deletion[K], - getLedgerName func(K) string, -) map[string]int64 { - deltas := make(map[string]int64) - - for i := range updates { - if !updates[i].Old.IsDefined() { - deltas[getLedgerName(updates[i].Key)]++ - } - } - - for i := range deletions { - deltas[getLedgerName(deletions[i].Key)]-- - } - - return deltas -} - // applyDelta safely adds a signed delta to a uint64 counter, clamping at zero on underflow. func applyDelta(current uint64, delta int64) uint64 { if delta >= 0 { @@ -77,11 +54,14 @@ func countVolumeDeltas(updates []attributes.Update[domain.VolumeKey, *raftcmdpb. // updateBoundaryCounters computes attribute key deltas and updates LedgerBoundaries // for each affected ledger. Must be called before Derived.Boundaries.Merge(). // -// Only the two attribute-derived counters (volume_count, metadata_count) live -// here now — reference_count, posting_count, revert_count, numscript_execution_count, -// ephemeral_evicted_count and transient_used_count all migrated to the -// usagebuilder (EN-1420) which derives them from the audit chain instead. -// The two remaining counters are covered by EN-1422. +// Only volume_count lives here now — reference_count, posting_count, +// revert_count, numscript_execution_count, ephemeral_evicted_count and +// transient_used_count all migrated to the usagebuilder (EN-1420) which +// derives them from the audit chain. metadata_count was dropped: the +// admission preload no longer injects the old value for metadata keys, +// so `Old.IsDefined()` no longer distinguishes "new key" from "overwrite" +// — the counter drifted from cardinality to write-event-count. It will +// come back on a sound foundation later. // // purgedVolumes and transientVolumes are still consumed here — not to feed // their own counter (that lives in the usagebuilder), but to correctly @@ -92,8 +72,6 @@ func (b *WriteSet) updateBoundaryCounters( volumeUpdates []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], purgedVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], transientVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], - metadataUpdates []attributes.Update[domain.MetadataKey, *commonpb.MetadataValue], - metadataDeletions []attributes.Deletion[domain.MetadataKey], ) { volumeDeltas := countVolumeDeltas(volumeUpdates) @@ -106,18 +84,7 @@ func (b *WriteSet) updateBoundaryCounters( volumeDeltas[transientVolumes[i].Key.LedgerName]-- } - metadataDeltas := countKeyDeltas(metadataUpdates, metadataDeletions, func(k domain.MetadataKey) string { return k.LedgerName }) - - // Collect all affected ledgers. - affected := make(map[string]struct{}) - for ledger := range volumeDeltas { - affected[ledger] = struct{}{} - } - for ledger := range metadataDeltas { - affected[ledger] = struct{}{} - } - - for ledgerName := range affected { + for ledgerName := range volumeDeltas { boundariesReader, err := b.Boundaries().Get(domain.LedgerKey{Name: ledgerName}) if err != nil { continue @@ -125,7 +92,6 @@ func (b *WriteSet) updateBoundaryCounters( boundaries := boundariesReader.Mutate() boundaries.VolumeCount = applyDelta(boundaries.GetVolumeCount(), volumeDeltas[ledgerName]) - boundaries.MetadataCount = applyDelta(boundaries.GetMetadataCount(), metadataDeltas[ledgerName]) b.Boundaries().Put(domain.LedgerKey{Name: ledgerName}, boundaries) } } diff --git a/internal/proto/commonpb/common.pb.go b/internal/proto/commonpb/common.pb.go index 82ad554ec3..4016aebd3f 100644 --- a/internal/proto/commonpb/common.pb.go +++ b/internal/proto/commonpb/common.pb.go @@ -11734,7 +11734,6 @@ type LedgerStats struct { state protoimpl.MessageState `protogen:"open.v1"` TransactionCount uint64 `protobuf:"fixed64,1,opt,name=transaction_count,json=transactionCount,proto3" json:"transaction_count,omitempty"` VolumeCount uint64 `protobuf:"fixed64,2,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` - MetadataCount uint64 `protobuf:"fixed64,3,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` ReferenceCount uint64 `protobuf:"fixed64,4,opt,name=reference_count,json=referenceCount,proto3" json:"reference_count,omitempty"` PostingCount uint64 `protobuf:"fixed64,5,opt,name=posting_count,json=postingCount,proto3" json:"posting_count,omitempty"` EphemeralEvictedCount uint64 `protobuf:"fixed64,6,opt,name=ephemeral_evicted_count,json=ephemeralEvictedCount,proto3" json:"ephemeral_evicted_count,omitempty"` @@ -11790,13 +11789,6 @@ func (x *LedgerStats) GetVolumeCount() uint64 { return 0 } -func (x *LedgerStats) GetMetadataCount() uint64 { - if x != nil { - return x.MetadataCount - } - return 0 -} - func (x *LedgerStats) GetReferenceCount() uint64 { if x != nil { return x.ReferenceCount @@ -13264,11 +13256,10 @@ const file_common_proto_rawDesc = "" + "\bprevious\x18\x03 \x01(\tR\bprevious\x12\x12\n" + "\x04next\x18\x04 \x01(\tR\x04next\x122\n" + "\faccount_data\x18\x05 \x03(\v2\x0f.common.AccountR\vaccountData\x12>\n" + - "\x10transaction_data\x18\x06 \x03(\v2\x13.common.TransactionR\x0ftransactionData\"\xb8\x03\n" + + "\x10transaction_data\x18\x06 \x03(\v2\x13.common.TransactionR\x0ftransactionData\"\x91\x03\n" + "\vLedgerStats\x12+\n" + "\x11transaction_count\x18\x01 \x01(\x06R\x10transactionCount\x12!\n" + - "\fvolume_count\x18\x02 \x01(\x06R\vvolumeCount\x12%\n" + - "\x0emetadata_count\x18\x03 \x01(\x06R\rmetadataCount\x12'\n" + + "\fvolume_count\x18\x02 \x01(\x06R\vvolumeCount\x12'\n" + "\x0freference_count\x18\x04 \x01(\x06R\x0ereferenceCount\x12#\n" + "\rposting_count\x18\x05 \x01(\x06R\fpostingCount\x126\n" + "\x17ephemeral_evicted_count\x18\x06 \x01(\x06R\x15ephemeralEvictedCount\x120\n" + diff --git a/internal/proto/commonpb/common_reader.pb.go b/internal/proto/commonpb/common_reader.pb.go index f980537270..ee0744112d 100644 --- a/internal/proto/commonpb/common_reader.pb.go +++ b/internal/proto/commonpb/common_reader.pb.go @@ -11891,7 +11891,6 @@ func NewPreparedQueryCursorListReader(s []*PreparedQueryCursor) PreparedQueryCur type LedgerStatsReader interface { GetTransactionCount() uint64 GetVolumeCount() uint64 - GetMetadataCount() uint64 GetReferenceCount() uint64 GetPostingCount() uint64 GetEphemeralEvictedCount() uint64 @@ -11912,10 +11911,6 @@ func (r *ledgerStatsReadonly) GetVolumeCount() uint64 { return (*LedgerStats)(r).GetVolumeCount() } -func (r *ledgerStatsReadonly) GetMetadataCount() uint64 { - return (*LedgerStats)(r).GetMetadataCount() -} - func (r *ledgerStatsReadonly) GetReferenceCount() uint64 { return (*LedgerStats)(r).GetReferenceCount() } diff --git a/internal/proto/commonpb/common_vtproto.pb.go b/internal/proto/commonpb/common_vtproto.pb.go index 73343c14bf..165fa61b93 100644 --- a/internal/proto/commonpb/common_vtproto.pb.go +++ b/internal/proto/commonpb/common_vtproto.pb.go @@ -4233,7 +4233,6 @@ func (m *LedgerStats) CloneVT() *LedgerStats { r := new(LedgerStats) r.TransactionCount = m.TransactionCount r.VolumeCount = m.VolumeCount - r.MetadataCount = m.MetadataCount r.ReferenceCount = m.ReferenceCount r.PostingCount = m.PostingCount r.EphemeralEvictedCount = m.EphemeralEvictedCount @@ -11601,9 +11600,6 @@ func (this *LedgerStats) EqualVT(that *LedgerStats) bool { if this.VolumeCount != that.VolumeCount { return false } - if this.MetadataCount != that.MetadataCount { - return false - } if this.ReferenceCount != that.ReferenceCount { return false } @@ -22298,12 +22294,6 @@ func (m *LedgerStats) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x21 } - if m.MetadataCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.MetadataCount)) - i-- - dAtA[i] = 0x19 - } if m.VolumeCount != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.VolumeCount)) @@ -27312,9 +27302,6 @@ func (m *LedgerStats) SizeVT() (n int) { if m.VolumeCount != 0 { n += 9 } - if m.MetadataCount != 0 { - n += 9 - } if m.ReferenceCount != 0 { n += 9 } @@ -51518,16 +51505,6 @@ func (m *LedgerStats) UnmarshalVT(dAtA []byte) error { } m.VolumeCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 3: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field MetadataCount", wireType) - } - m.MetadataCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.MetadataCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 case 4: if wireType != 1 { return fmt.Errorf("proto: wrong wireType = %d for field ReferenceCount", wireType) diff --git a/internal/proto/raftcmdpb/raft_cmd.pb.go b/internal/proto/raftcmdpb/raft_cmd.pb.go index 1c4bf91b52..d70ef1a315 100644 --- a/internal/proto/raftcmdpb/raft_cmd.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd.pb.go @@ -4886,7 +4886,6 @@ type LedgerBoundaries struct { NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` - MetadataCount uint64 `protobuf:"fixed64,4,opt,name=metadata_count,json=metadataCount,proto3" json:"metadata_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4942,13 +4941,6 @@ func (x *LedgerBoundaries) GetVolumeCount() uint64 { return 0 } -func (x *LedgerBoundaries) GetMetadataCount() uint64 { - if x != nil { - return x.MetadataCount - } - return 0 -} - type VolumePair struct { state protoimpl.MessageState `protogen:"open.v1"` Input *commonpb.Uint256 `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` @@ -6374,12 +6366,11 @@ const file_raft_cmd_proto_rawDesc = "" + "\vcreated_log\x18\x01 \x01(\v2\v.common.LogH\x00R\n" + "createdLog\x12/\n" + "\x12reference_sequence\x18\x02 \x01(\x06H\x00R\x11referenceSequenceB\x06\n" + - "\x04type\"\xac\x01\n" + + "\x04type\"\x85\x01\n" + "\x10LedgerBoundaries\x12.\n" + "\x13next_transaction_id\x18\x01 \x01(\x06R\x11nextTransactionId\x12\x1e\n" + "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\x12!\n" + - "\fvolume_count\x18\x03 \x01(\x06R\vvolumeCount\x12%\n" + - "\x0emetadata_count\x18\x04 \x01(\x06R\rmetadataCount\"\\\n" + + "\fvolume_count\x18\x03 \x01(\x06R\vvolumeCount\"\\\n" + "\n" + "VolumePair\x12%\n" + "\x05input\x18\x01 \x01(\v2\x0f.common.Uint256R\x05input\x12'\n" + diff --git a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go index c96f29ff44..16be100d8c 100644 --- a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go @@ -5250,7 +5250,6 @@ type LedgerBoundariesReader interface { GetNextTransactionId() uint64 GetNextLogId() uint64 GetVolumeCount() uint64 - GetMetadataCount() uint64 Mutate() *LedgerBoundaries } @@ -5268,10 +5267,6 @@ func (r *ledgerBoundariesReadonly) GetVolumeCount() uint64 { return (*LedgerBoundaries)(r).GetVolumeCount() } -func (r *ledgerBoundariesReadonly) GetMetadataCount() uint64 { - return (*LedgerBoundaries)(r).GetMetadataCount() -} - func (r *ledgerBoundariesReadonly) Mutate() *LedgerBoundaries { return (*LedgerBoundaries)(r).CloneVT() } diff --git a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go index e3a44db4b6..93b7015712 100644 --- a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go @@ -1961,7 +1961,6 @@ func (m *LedgerBoundaries) CloneVT() *LedgerBoundaries { r.NextTransactionId = m.NextTransactionId r.NextLogId = m.NextLogId r.VolumeCount = m.VolumeCount - r.MetadataCount = m.MetadataCount if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -5806,9 +5805,6 @@ func (this *LedgerBoundaries) EqualVT(that *LedgerBoundaries) bool { if this.VolumeCount != that.VolumeCount { return false } - if this.MetadataCount != that.MetadataCount { - return false - } return string(this.unknownFields) == string(that.unknownFields) } @@ -11058,12 +11054,6 @@ func (m *LedgerBoundaries) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.MetadataCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.MetadataCount)) - i-- - dAtA[i] = 0x21 - } if m.VolumeCount != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.VolumeCount)) @@ -14338,9 +14328,6 @@ func (m *LedgerBoundaries) SizeVT() (n int) { if m.VolumeCount != 0 { n += 9 } - if m.MetadataCount != 0 { - n += 9 - } n += len(m.unknownFields) return n } @@ -25518,16 +25505,6 @@ func (m *LedgerBoundaries) UnmarshalVT(dAtA []byte) error { } m.VolumeCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 4: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field MetadataCount", wireType) - } - m.MetadataCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.MetadataCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/misc/proto/common.proto b/misc/proto/common.proto index 779c9b0f4a..2e01041951 100644 --- a/misc/proto/common.proto +++ b/misc/proto/common.proto @@ -1492,7 +1492,6 @@ message PreparedQueryCursor { message LedgerStats { fixed64 transaction_count = 1; fixed64 volume_count = 2; - fixed64 metadata_count = 3; fixed64 reference_count = 4; fixed64 posting_count = 5; fixed64 ephemeral_evicted_count = 6; diff --git a/misc/proto/raft_cmd.proto b/misc/proto/raft_cmd.proto index fdf3eaaee5..5c03c20512 100644 --- a/misc/proto/raft_cmd.proto +++ b/misc/proto/raft_cmd.proto @@ -667,7 +667,6 @@ message LedgerBoundaries { fixed64 next_transaction_id = 1; fixed64 next_log_id = 2; fixed64 volume_count = 3; - fixed64 metadata_count = 4; } message VolumePair { diff --git a/tests/e2e/business/stats_test.go b/tests/e2e/business/stats_test.go index 758df4d58b..5032c380a7 100644 --- a/tests/e2e/business/stats_test.go +++ b/tests/e2e/business/stats_test.go @@ -32,7 +32,6 @@ var _ = Describe("GetLedgerStats", Ordered, func() { Expect(resp.TransactionCount).To(BeZero()) Expect(resp.PostingCount).To(BeZero()) Expect(resp.VolumeCount).To(BeZero()) - Expect(resp.MetadataCount).To(BeZero()) Expect(resp.ReferenceCount).To(BeZero()) }) }) @@ -69,7 +68,6 @@ var _ = Describe("GetLedgerStats", Ordered, func() { g.Expect(resp.PostingCount).To(Equal(uint64(3))) // world/USD + bank:main/USD + bank:fees/USD + users:alice/USD = 4 g.Expect(resp.VolumeCount).To(Equal(uint64(4))) - g.Expect(resp.MetadataCount).To(BeZero()) g.Expect(resp.ReferenceCount).To(BeZero()) }).Should(Succeed()) }) From fe7e0b12cd2f4e4dc92c221c4d74a1240c26e9fb Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 09:26:34 +0200 Subject: [PATCH 04/35] feat(EN-1422): migrate VolumeCount to usagebuilder via new_volumes on LedgerLog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VolumeCount was the last non-ID-generator field on LedgerBoundaries. Move it to the usagebuilder side-store, derived from a new LedgerLog.new_volumes field that the FSM populates alongside the existing purged_volumes list. Add `repeated TouchedVolume new_volumes = 5` to `LedgerLog` (parallel to `purged_volumes`). Semantics: - new_volumes lists (account, asset) tuples whose PERSISTENT entry was newly created by this log — i.e. no preloaded prior value or the zero placeholder. - Transient volumes (never persisted) are excluded by construction. - Ephemeral volumes appear in BOTH new_volumes AND purged_volumes for the same log: they were persisted briefly then evicted post-commit. The usagebuilder computes CounterVolume = sum(new) - sum(purged) so ephemeral volumes contribute +0 net. - Remove `write_set_counters.go` entirely (VolumeCount was the last counter still maintained on LedgerBoundaries). - Add `write_set_new_volumes.go` with `isVolumePreloadZero`, `newPersistentVolumeKey`, `makeNewKeySet`, and `buildNewByLog` — parallel to the ephemeral-purge helpers. - `write_set.go`: compute `newByLog` from `partResult.kept ∪ partResult.purged` filtered on "prior undefined or zero-placeholder", inject into `ledgerLog.NewVolumes` in the same loop that already injects `PurgedVolumes`. The updateBoundaryCounters call is gone. - Delete `volume_count` from `LedgerBoundaries` proto — no fields remain outside the two ID generators. - New counter ID `CounterVolume = 0x07` in usagestore/keys.go. - `process_audit.go`: `logCounts` gains `newVolumes`; `applyVolumeDelta` helper feeds CounterVolume with `newVolumes - purged` per log. Dispatched from both dispatchCreateTransaction and dispatchRevertTransaction. - `controller_default.go`: VolumeCount read from usagestore via the existing snapshot, alongside the six event counters. Document explicitly that admission's volume preload is load-bearing for the new_volumes projection. Unlike the metadata preload (removed opportunistically), volume preload is structurally required by balance checks / Uint256 arithmetic / numscript resolution and is a stable long-term contract. If a future refactor considers alleviating it, VolumeCount must be re-hosted first. Same reset semantics as EN-1420: existing ledgers restart at 0 and repopulate from the earliest audit entry still reachable in Pebble. `ledgerctl store rebuild-usage` picks up the new counter automatically. - Checker pass `compareVolumeCount` (audit-replay verification of the new counter). Follow-up — the existing checker infrastructure for purged/transient (compareExclusionProjections) provides the template. - `metadata_count` restoration — still open, no ticket. --- AGENTS.md | 2 + docs/technical/architecture/overview.md | 3 +- .../application/ctrl/controller_default.go | 28 +- .../application/usagebuilder/process_audit.go | 38 +- internal/infra/state/write_set.go | 54 +- internal/infra/state/write_set_counters.go | 97 -- internal/infra/state/write_set_new_volumes.go | 134 ++ internal/proto/commonpb/common.pb.go | 1345 +++++++---------- internal/proto/commonpb/common_dethash.pb.go | 9 + internal/proto/commonpb/common_reader.pb.go | 5 + internal/proto/commonpb/common_vtproto.pb.go | 76 + internal/proto/raftcmdpb/raft_cmd.pb.go | 13 +- .../proto/raftcmdpb/raft_cmd_reader.pb.go | 5 - .../proto/raftcmdpb/raft_cmd_vtproto.pb.go | 23 - internal/storage/usagestore/keys.go | 1 + misc/proto/common.proto | 9 + misc/proto/raft_cmd.proto | 1 - 17 files changed, 897 insertions(+), 946 deletions(-) delete mode 100644 internal/infra/state/write_set_counters.go create mode 100644 internal/infra/state/write_set_new_volumes.go diff --git a/AGENTS.md b/AGENTS.md index 9ba3e493f8..7a49a8299a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,8 @@ This document contains rules and conventions for AI agents working on this codeb 4. **Pebble writes only from the hot path or declared lifecycle paths** — `*dal.Store.OpenWriteSession()` is the only producer of `*dal.WriteSession`. Outside the FSM hot path, only declared lifecycle paths may call it: `internal/bootstrap/config_validation.go`, `internal/infra/backup/`, `internal/infra/attributes/prepare.go`. This is enforced by a `forbidigo` rule in `.golangci.yaml`; new call sites must be added to the exclusions block with a justification. 5. **Never delete cache entries outside of rotations** — Cache entries must only be evicted during generation rotations (Gen0 → Gen1 → discard). Deleting individual entries breaks the cache prediction mechanism (bloom filters, tombstones). 6. **Every FSM `Registry.X.Get(...)` must have a matching preload, declared by the component that proposes the command** — The FSM apply path reads from the in-memory cache; a cache miss turns the read into a silent no-op. Each component that emits a proposal (the metadata converter, the index builder, the cluster-config reconciler, the idempotency-eviction scheduler, the mirror worker, admission) is responsible for declaring its own `preload.Needs` covering every key its apply path will read. There is NO central proposal→needs registry — coupling the preload package to every proposal type creates a single point that easily falls behind. The component knows what it reads; the component declares it. The shared `proposeTechnical` helper takes a `*preload.Needs` parameter the caller fills in (pass nil or an empty `Needs` when the apply path has no cache-keyed reads, e.g. cluster config / idempotency eviction). The preload populates the cache via `MirrorPreload` with a fresh value read at propose time (Pebble fallback on cache miss), and `PredictedIndex` catches mutations between propose and apply. + + **Volume preload is load-bearing for the `LedgerLog.new_volumes` projection.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, inflates `usagestore.CounterVolume`, and only surfaces when the checker's volume-count pass runs. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. 7. **Never silently skip a "should not happen" branch** — A branch that is reachable only if an invariant is violated (nil where the contract says non-nil, a state we believe unreachable, a cache miss after a guaranteed preload, etc.) MUST surface a loud signal: `return fmt.Errorf("invariant: ...")` so it bubbles up, or `assert.Unreachable(...)` for SUT-level invariants exercised under antithesis. A silent `return nil` / `continue` on these branches hides real bugs — particularly catastrophic in the FSM apply path, where a no-op desyncs nodes from each other. Branches that represent genuine runtime conditions (cache miss as an expected outcome, stale proposal, deleted entity) keep their soft `return nil`. The distinction is whether the case is *expected* (soft skip OK) or *impossible by design* (must fail loudly). The comment must say *why* the case is impossible so a reader can decide whether to add a hard fail or relax the rule. 8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus); extend the list as new persisted projections land. diff --git a/docs/technical/architecture/overview.md b/docs/technical/architecture/overview.md index 239b77fa97..db2bf44a62 100644 --- a/docs/technical/architecture/overview.md +++ b/docs/technical/architecture/overview.md @@ -154,11 +154,10 @@ Per-ledger boundaries use the `LedgerBoundaries` protobuf message from `raft_cmd message LedgerBoundaries { fixed64 next_transaction_id = 1; fixed64 next_log_id = 2; - fixed64 volume_count = 3; } ``` -> Event counters (postings, reverts, numscript executions, references, ephemeral evictions, transient volumes) live in the usagebuilder side-store (`internal/application/usagebuilder`, `internal/storage/usagestore`) — they are derived from the audit chain, not maintained on `LedgerBoundaries`. `metadata_count` is temporarily removed pending a sound foundation (the admission preload no longer injects old metadata values, breaking the cardinality delta logic). +> Every per-ledger counter — postings, reverts, numscript executions, references, ephemeral evictions, transient volumes, **and volumes** — lives in the usagebuilder side-store (`internal/application/usagebuilder`, `internal/storage/usagestore`). All counters are derived from the audit chain: event counts (posting / revert / numscript-exec / reference) come from replaying orders; ephemeral / transient counts come from `LedgerLog.PurgedVolumes` and `AppliedProposal.TransientVolumes`; the volume count comes from the FSM-enriched `LedgerLog.NewVolumes` field paired with `PurgedVolumes` (delta = new − purged). `LedgerBoundaries` carries only the two ID generators that drive apply-time allocation decisions. `metadata_count` is intentionally not exposed — the admission preload no longer injects old metadata values so cardinality cannot be reliably derived at the FSM level; it will come back on a sound foundation later. > **Note:** The `State` / `LedgerState` proto messages sometimes shown in older documentation are conceptual models, not actual proto definitions. The real FSM state is spread across the `Machine` struct fields and its `StateRegistry`. diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index 5b9e77314f..098e3e137b 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -502,17 +502,19 @@ func (ctrl *DefaultController) GetAccount(ctx context.Context, ledgerName string } // GetLedgerStats returns aggregate statistics for a ledger. -// TransactionCount, LogCount and VolumeCount come from the LedgerBoundaries -// attribute. Every event-count field (PostingCount, RevertCount, -// NumscriptExecutionCount, ReferenceCount, EphemeralEvictedCount, -// TransientUsedCount) is derived from the audit chain by the usagebuilder -// and read from the usagestore side-store — expect up to one usagebuilder -// tick interval of lag behind the live FSM (see EN-1420). +// TransactionCount and LogCount come from the LedgerBoundaries attribute — +// they are ID-generator state used by the FSM itself. Every projected +// counter (PostingCount, RevertCount, NumscriptExecutionCount, +// ReferenceCount, EphemeralEvictedCount, TransientUsedCount, VolumeCount) +// is derived from the audit chain by the usagebuilder and read from the +// usagestore side-store through a single Pebble snapshot — expect up to +// one usagebuilder tick interval of lag behind the live FSM. See EN-1420 +// and EN-1422. // // MetadataCount is intentionally not returned: the admission preload no -// longer injects old metadata values, so the FSM-side counter can no longer -// distinguish "new key" from "overwrite" and is disabled until it comes -// back on a sound foundation. +// longer injects old metadata values, so the FSM-side counter could no +// longer distinguish "new key" from "overwrite". It is disabled until it +// comes back on a sound foundation (open question, no ticket yet). func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName string) (*commonpb.LedgerStats, error) { handle, err := ctrl.store.NewReadHandle() if err != nil { @@ -540,14 +542,12 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st stats.TransactionCount = nextTxID - 1 } - stats.VolumeCount = boundaries.GetVolumeCount() - if nextLogID := boundaries.GetNextLogId(); nextLogID > 0 { stats.LogCount = nextLogID - 1 } } - // Event-count fields from the usagebuilder side-store. All six reads go + // Projected counters from the usagebuilder side-store. All reads go // against a single Pebble snapshot so a concurrent usagebuilder commit // cannot land a partial view between them. Missing keys read as 0. usageSnap := ctrl.usageStore.NewSnapshot() @@ -577,6 +577,10 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st return nil, fmt.Errorf("reading transient used counter: %w", err) } + if stats.VolumeCount, err = usageSnap.GetCounter(ledgerName, usagestore.CounterVolume); err != nil { + return nil, fmt.Errorf("reading volume counter: %w", err) + } + return &stats, nil } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index f8b1777ad0..b093040530 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -283,8 +283,20 @@ func (b *Builder) dispatchOrder( return nil } +// applyVolumeDelta feeds CounterVolume with the new-minus-purged delta from +// a single log. Ephemeral volumes cancel out because they appear in both +// lists — same log, contribution +0 net. This is the counter's cardinality +// semantics: sum(new_persistent) - sum(purged_persistent). +func applyVolumeDelta(ledger string, counts logCounts, state *batchState) { + delta := counterDelta(counts.newVolumes) - counterDelta(counts.purged) + if delta != 0 { + state.addCounter(ledger, usagestore.CounterVolume, delta) + } +} + // dispatchCreateTransaction increments posting, reference, numscript-exec, -// ephemeral-evicted and template usage counters for a create-tx order. +// ephemeral-evicted, volume and template usage counters for a create-tx +// order. func (b *Builder) dispatchCreateTransaction( ctx context.Context, handle dal.PebbleGetter, @@ -309,6 +321,8 @@ func (b *Builder) dispatchCreateTransaction( state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) } + applyVolumeDelta(ledger, counts, state) + if order.GetReference() != "" { state.addCounter(ledger, usagestore.CounterReference, 1) } @@ -327,9 +341,9 @@ func (b *Builder) dispatchCreateTransaction( return nil } -// dispatchRevertTransaction increments revert, posting and ephemeral-evicted -// counters for a revert-tx order. The resolved reverse-postings + purged -// volumes live on the produced log. +// dispatchRevertTransaction increments revert, posting, ephemeral-evicted +// and volume counters for a revert-tx order. The resolved reverse-postings, +// purged volumes and newly-created volumes live on the produced log. func (b *Builder) dispatchRevertTransaction( ctx context.Context, handle dal.PebbleGetter, @@ -352,14 +366,17 @@ func (b *Builder) dispatchRevertTransaction( state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) } + applyVolumeDelta(ledger, counts, state) + return nil } // logCounts is the tuple of per-log counter deltas extracted from a single // LedgerLog payload. type logCounts struct { - postings int - purged int + postings int + purged int + newVolumes int } // countsFromLog fetches the log at logSeq and returns the resolved posting @@ -386,10 +403,13 @@ func (b *Builder) countsFromLog(ctx context.Context, handle dal.PebbleGetter, lo return logCounts{}, nil } - // PurgedVolumes lives on LedgerLog directly (not on the payload variant), - // so we can extract it independently of the payload type. + // PurgedVolumes and NewVolumes live on LedgerLog directly (not on the + // payload variant), so we can extract them independently of the payload + // type. Ephemeral (account, asset) tuples appear in both lists — the + // caller cancels them out when computing the live VolumeCount delta. result := logCounts{ - purged: len(ledgerLog.GetPurgedVolumes()), + purged: len(ledgerLog.GetPurgedVolumes()), + newVolumes: len(ledgerLog.GetNewVolumes()), } if ledgerLog.GetData() == nil { diff --git a/internal/infra/state/write_set.go b/internal/infra/state/write_set.go index fb566a9818..4320d9e9ce 100644 --- a/internal/infra/state/write_set.go +++ b/internal/infra/state/write_set.go @@ -102,6 +102,15 @@ type WriteSet struct { // LedgerLog.purged_volumes before AppendLogs. purgedByLog [][]*commonpb.TouchedVolume + // newByLog[i] is the deduplicated list of (account, asset) volumes + // whose persistent entry was newly created by the log produced by + // order i. Computed during Merge from volumes.Slots() intersected + // with the set of persistent updates whose preloaded prior value was + // undefined or the zero placeholder. Injected into each + // LedgerLog.new_volumes before AppendLogs. Feeds the usagebuilder's + // VolumeCount projection — see EN-1422. + newByLog [][]*commonpb.TouchedVolume + // bloomUpdates collects canonical keys per attribute type during Merge // for bloom filter updates before batch.Commit(). bloomUpdates bloom.BloomUpdates @@ -257,11 +266,13 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create return fmt.Errorf("failed to merge references: %w", err) } - // Update per-ledger attribute counters in boundaries before merging them. - // metadataUpdates / metadataDeletions no longer contribute to any - // boundary counter (see write_set_counters.go) — they still flow through - // to the cache flush below. - b.updateBoundaryCounters(volumeUpdates, partResult.purged, partResult.transient) + // No boundary-counter mutation here anymore: every per-ledger counter + // moved to the usagebuilder subsystem (EN-1420 / EN-1422). LedgerBoundaries + // carries only the two ID generators now, and they are mutated by the + // posting producers directly on the boundaries reader. + // + // The `new_volumes` list is computed post-merge alongside `purged_volumes` + // — see the log-injection block below. boundaryUpdates, boundaryDeletions, err := b.Derived.Boundaries.Merge() if err != nil { @@ -401,12 +412,21 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create } // Build createdLogs (skipping idempotency replays) and inject the - // per-log purged_volumes subset before persisting. purgedByLog is - // indexed by ORDER index — same as logsOrRefs — so the mapping uses - // the loop's i, not the createdLogs append index. + // per-log purged_volumes + new_volumes subsets before persisting. + // purgedByLog and newByLog are indexed by ORDER index — same as + // logsOrRefs — so the mapping uses the loop's i, not the createdLogs + // append index. purgedSet := makePurgedKeySet(partResult.purged) b.purgedByLog = buildPurgedByLog(b.volumes.Slots(), purgedSet) + // New persistent volumes = kept + purged filtered on "prior value + // undefined or zero placeholder". Transient volumes are excluded by + // construction — they never reach the attribute store. The + // usagebuilder computes CounterVolume = sum(new_volumes) - sum(purged_volumes) + // across the audit chain, so ephemeral volumes contribute +0 net. + newSet := makeNewKeySet(partResult.kept, partResult.purged) + b.newByLog = buildNewByLog(b.volumes.Slots(), newSet) + createdLogs := make([]*commonpb.Log, 0, len(logsOrRefs)) for i, lr := range logsOrRefs { log := lr.GetCreatedLog() @@ -417,17 +437,25 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create continue } - if i < len(b.purgedByLog) && len(b.purgedByLog[i]) > 0 { + hasPurged := i < len(b.purgedByLog) && len(b.purgedByLog[i]) > 0 + hasNew := i < len(b.newByLog) && len(b.newByLog[i]) > 0 + + if hasPurged || hasNew { apply := log.GetPayload().GetApply() if apply == nil { - return fmt.Errorf("invariant: order %d produced purged volumes %v but its log payload is not an ApplyLedgerLog (payload=%T)", - i, b.purgedByLog[i], log.GetPayload()) + return fmt.Errorf("invariant: order %d produced volume annotations but its log payload is not an ApplyLedgerLog (payload=%T)", + i, log.GetPayload()) } ledgerLog := apply.GetLog() if ledgerLog == nil { - return fmt.Errorf("invariant: order %d produced purged volumes %v but its ApplyLedgerLog carries no LedgerLog", i, b.purgedByLog[i]) + return fmt.Errorf("invariant: order %d produced volume annotations but its ApplyLedgerLog carries no LedgerLog", i) + } + if hasPurged { + ledgerLog.PurgedVolumes = b.purgedByLog[i] + } + if hasNew { + ledgerLog.NewVolumes = b.newByLog[i] } - ledgerLog.PurgedVolumes = b.purgedByLog[i] } createdLogs = append(createdLogs, log) diff --git a/internal/infra/state/write_set_counters.go b/internal/infra/state/write_set_counters.go deleted file mode 100644 index 3e2f5d777e..0000000000 --- a/internal/infra/state/write_set_counters.go +++ /dev/null @@ -1,97 +0,0 @@ -package state - -import ( - "github.com/formancehq/ledger/v3/internal/domain" - "github.com/formancehq/ledger/v3/internal/infra/attributes" - "github.com/formancehq/ledger/v3/internal/proto/raftcmdpb" -) - -// applyDelta safely adds a signed delta to a uint64 counter, clamping at zero on underflow. -func applyDelta(current uint64, delta int64) uint64 { - if delta >= 0 { - return current + uint64(delta) - } - - sub := uint64(-delta) - if sub > current { - return 0 - } - - return current - sub -} - -// isVolumePreloadZero returns true if the volume pair is the zero placeholder -// injected by the preloader for keys that don't exist in Pebble. -// Unlike isVolumeZeroBalance (input == output), this checks input == 0 AND output == 0. -func isVolumePreloadZero(v *raftcmdpb.VolumePair) bool { - if v == nil { - return true - } - - in := v.GetInput() - out := v.GetOutput() - - return (in == nil || (in.GetV0() == 0 && in.GetV1() == 0 && in.GetV2() == 0 && in.GetV3() == 0)) && - (out == nil || (out.GetV0() == 0 && out.GetV1() == 0 && out.GetV2() == 0 && out.GetV3() == 0)) -} - -// countVolumeDeltas counts per-ledger new volume keys. -// A volume is "new" if Old is either undefined or a zero preload placeholder -// (the preloader always seeds missing volumes with {0,0} in the cache). -func countVolumeDeltas(updates []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) map[string]int64 { - deltas := make(map[string]int64) - - for i := range updates { - old := updates[i].Old - if !old.IsDefined() || isVolumePreloadZero(old.Value()) { - deltas[updates[i].Key.LedgerName]++ - } - } - - return deltas -} - -// updateBoundaryCounters computes attribute key deltas and updates LedgerBoundaries -// for each affected ledger. Must be called before Derived.Boundaries.Merge(). -// -// Only volume_count lives here now — reference_count, posting_count, -// revert_count, numscript_execution_count, ephemeral_evicted_count and -// transient_used_count all migrated to the usagebuilder (EN-1420) which -// derives them from the audit chain. metadata_count was dropped: the -// admission preload no longer injects the old value for metadata keys, -// so `Old.IsDefined()` no longer distinguishes "new key" from "overwrite" -// — the counter drifted from cardinality to write-event-count. It will -// come back on a sound foundation later. -// -// purgedVolumes and transientVolumes are still consumed here — not to feed -// their own counter (that lives in the usagebuilder), but to correctly -// subtract them from volume_count: an ephemeral volume that never survives -// commit and a transient volume that never persists must not count as a live -// volume-store entry. -func (b *WriteSet) updateBoundaryCounters( - volumeUpdates []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], - purgedVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], - transientVolumes []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair], -) { - volumeDeltas := countVolumeDeltas(volumeUpdates) - - // Ephemeral volumes are purged after commit — subtract from volume count. - for i := range purgedVolumes { - volumeDeltas[purgedVolumes[i].Key.LedgerName]-- - } - // Transient volumes are never persisted — subtract from volume count. - for i := range transientVolumes { - volumeDeltas[transientVolumes[i].Key.LedgerName]-- - } - - for ledgerName := range volumeDeltas { - boundariesReader, err := b.Boundaries().Get(domain.LedgerKey{Name: ledgerName}) - if err != nil { - continue - } - - boundaries := boundariesReader.Mutate() - boundaries.VolumeCount = applyDelta(boundaries.GetVolumeCount(), volumeDeltas[ledgerName]) - b.Boundaries().Put(domain.LedgerKey{Name: ledgerName}, boundaries) - } -} diff --git a/internal/infra/state/write_set_new_volumes.go b/internal/infra/state/write_set_new_volumes.go new file mode 100644 index 0000000000..5e1ce9623a --- /dev/null +++ b/internal/infra/state/write_set_new_volumes.go @@ -0,0 +1,134 @@ +package state + +import ( + "sort" + + "github.com/formancehq/ledger/v3/internal/domain" + "github.com/formancehq/ledger/v3/internal/infra/attributes" + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/proto/raftcmdpb" +) + +// isVolumePreloadZero returns true if the volume pair is the zero placeholder +// injected by the preloader for keys that don't exist in Pebble. Unlike +// isVolumeZeroBalance (input == output), this checks input == 0 AND output == 0 +// — the exact seed the preloader emits so admission's `Needs` can be planned +// deterministically. +func isVolumePreloadZero(v *raftcmdpb.VolumePair) bool { + if v == nil { + return true + } + + in := v.GetInput() + out := v.GetOutput() + + return (in == nil || (in.GetV0() == 0 && in.GetV1() == 0 && in.GetV2() == 0 && in.GetV3() == 0)) && + (out == nil || (out.GetV0() == 0 && out.GetV1() == 0 && out.GetV2() == 0 && out.GetV3() == 0)) +} + +// newPersistentVolumeKey is the (ledger, account, asset) tuple used by makeNewKeySet +// and buildNewByLog. Mirrors purgedVolumeKey — see write_set_ephemeral_purge.go +// for the asset-dimension rationale. +type newPersistentVolumeKey struct { + Ledger string + Account string + Asset string +} + +// makeNewKeySet builds a lookup set over the (ledger, account, asset) of every +// PERSISTENT volume that was newly created by this proposal. A volume is +// "new" iff its preloaded prior value was either undefined or the zero +// placeholder — i.e. the attribute store did not yet carry an entry for that +// (account, asset) tuple. +// +// Only persistent updates (kept + ephemeral) qualify. Transient volumes never +// hit the attribute store, so they are never "new" in the persisted sense — +// the caller passes only persistent slices. +func makeNewKeySet(persistentUpdates ...[]attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) map[newPersistentVolumeKey]struct{} { + total := 0 + for _, updates := range persistentUpdates { + total += len(updates) + } + + if total == 0 { + return nil + } + + set := make(map[newPersistentVolumeKey]struct{}, total) + + for _, updates := range persistentUpdates { + for i := range updates { + old := updates[i].Old + if old.IsDefined() && !isVolumePreloadZero(old.Value()) { + continue + } + + set[newPersistentVolumeKey{ + Ledger: updates[i].Key.LedgerName, + Account: updates[i].Key.Account, + Asset: updates[i].Key.Asset, + }] = struct{}{} + } + } + + return set +} + +// buildNewByLog produces, for each order index, the deduplicated list of +// (account, asset) tuples that the order touched and that the proposal +// classified as newly-created persistent volumes. Indexed by order_index; +// entries for orders that produced no new persistent volume are nil. Tuples +// within an entry are sorted (by account then asset) to keep the log payload +// deterministic across runs. +// +// Semantically parallel to buildPurgedByLog. Ephemeral volumes appear in +// BOTH new_volumes AND purged_volumes for the same log: they were persisted +// (hence "new") and evicted after commit (hence "purged"). The usagebuilder's +// VolumeCount = sum(new_volumes) - sum(purged_volumes) relies on that +// symmetry. +func buildNewByLog(perOrderVolumeKeys [][]domain.VolumeKey, newSet map[newPersistentVolumeKey]struct{}) [][]*commonpb.TouchedVolume { + if len(perOrderVolumeKeys) == 0 || len(newSet) == 0 { + return nil + } + + type accAsset struct{ Account, Asset string } + + out := make([][]*commonpb.TouchedVolume, len(perOrderVolumeKeys)) + for i, keys := range perOrderVolumeKeys { + if len(keys) == 0 { + continue + } + + seen := make(map[accAsset]struct{}, len(keys)) + for _, k := range keys { + if _, ok := newSet[newPersistentVolumeKey{Ledger: k.LedgerName, Account: k.Account, Asset: k.Asset}]; !ok { + continue + } + seen[accAsset{Account: k.Account, Asset: k.Asset}] = struct{}{} + } + + if len(seen) == 0 { + continue + } + + ordered := make([]accAsset, 0, len(seen)) + for k := range seen { + ordered = append(ordered, k) + } + sort.Slice(ordered, func(a, b int) bool { + if ordered[a].Account != ordered[b].Account { + return ordered[a].Account < ordered[b].Account + } + + return ordered[a].Asset < ordered[b].Asset + }) + + vols := make([]*commonpb.TouchedVolume, len(ordered)) + for j, k := range ordered { + vols[j] = &commonpb.TouchedVolume{Account: k.Account, Asset: k.Asset} + } + out[i] = vols + } + + return out +} diff --git a/internal/proto/commonpb/common.pb.go b/internal/proto/commonpb/common.pb.go index 4016aebd3f..0f300d090e 100644 --- a/internal/proto/commonpb/common.pb.go +++ b/internal/proto/commonpb/common.pb.go @@ -947,77 +947,6 @@ func (AccountTypePersistence) EnumDescriptor() ([]byte, []int) { return file_common_proto_rawDescGZIP(), []int{13} } -// AuditField enumerates the audit fields that can be filtered. The set is -// intentionally limited to what the audit access path can resolve efficiently -// (index lookup or key-range bound); NOT / non-indexed fields are rejected -// rather than degrading to a full-chain scan. -type AuditField int32 - -const ( - AuditField_AUDIT_FIELD_UNSPECIFIED AuditField = 0 - AuditField_AUDIT_FIELD_SEQUENCE AuditField = 1 // uint -> AuditEntry.sequence (audit-zone key range) - AuditField_AUDIT_FIELD_PROPOSAL_ID AuditField = 2 // uint -> AuditEntry.proposal_id (index range) - AuditField_AUDIT_FIELD_TIMESTAMP AuditField = 3 // uint -> AuditEntry.timestamp.data, unix micros (index range) - AuditField_AUDIT_FIELD_LOG_SEQUENCE AuditField = 4 // uint -> item log_sequence, match-any (index range) - AuditField_AUDIT_FIELD_OUTCOME AuditField = 5 // string in {success, failure} (index) - AuditField_AUDIT_FIELD_CALLER_SUBJECT AuditField = 6 // string -> caller_snapshot.identity.subject (index) - AuditField_AUDIT_FIELD_LEDGER AuditField = 7 // string -> AuditEntry.ledgers, match-any (index) - AuditField_AUDIT_FIELD_ORDER_TYPE AuditField = 8 // string -> order payload variant, match-any (index) -) - -// Enum value maps for AuditField. -var ( - AuditField_name = map[int32]string{ - 0: "AUDIT_FIELD_UNSPECIFIED", - 1: "AUDIT_FIELD_SEQUENCE", - 2: "AUDIT_FIELD_PROPOSAL_ID", - 3: "AUDIT_FIELD_TIMESTAMP", - 4: "AUDIT_FIELD_LOG_SEQUENCE", - 5: "AUDIT_FIELD_OUTCOME", - 6: "AUDIT_FIELD_CALLER_SUBJECT", - 7: "AUDIT_FIELD_LEDGER", - 8: "AUDIT_FIELD_ORDER_TYPE", - } - AuditField_value = map[string]int32{ - "AUDIT_FIELD_UNSPECIFIED": 0, - "AUDIT_FIELD_SEQUENCE": 1, - "AUDIT_FIELD_PROPOSAL_ID": 2, - "AUDIT_FIELD_TIMESTAMP": 3, - "AUDIT_FIELD_LOG_SEQUENCE": 4, - "AUDIT_FIELD_OUTCOME": 5, - "AUDIT_FIELD_CALLER_SUBJECT": 6, - "AUDIT_FIELD_LEDGER": 7, - "AUDIT_FIELD_ORDER_TYPE": 8, - } -) - -func (x AuditField) Enum() *AuditField { - p := new(AuditField) - *p = x - return p -} - -func (x AuditField) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (AuditField) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[14].Descriptor() -} - -func (AuditField) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[14] -} - -func (x AuditField) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use AuditField.Descriptor instead. -func (AuditField) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{14} -} - type AddressRole int32 const ( @@ -1051,11 +980,11 @@ func (x AddressRole) String() string { } func (AddressRole) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[15].Descriptor() + return file_common_proto_enumTypes[14].Descriptor() } func (AddressRole) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[15] + return &file_common_proto_enumTypes[14] } func (x AddressRole) Number() protoreflect.EnumNumber { @@ -1064,7 +993,7 @@ func (x AddressRole) Number() protoreflect.EnumNumber { // Deprecated: Use AddressRole.Descriptor instead. func (AddressRole) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{15} + return file_common_proto_rawDescGZIP(), []int{14} } type QueryTarget int32 @@ -1100,11 +1029,11 @@ func (x QueryTarget) String() string { } func (QueryTarget) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[16].Descriptor() + return file_common_proto_enumTypes[15].Descriptor() } func (QueryTarget) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[16] + return &file_common_proto_enumTypes[15] } func (x QueryTarget) Number() protoreflect.EnumNumber { @@ -1113,7 +1042,7 @@ func (x QueryTarget) Number() protoreflect.EnumNumber { // Deprecated: Use QueryTarget.Descriptor instead. func (QueryTarget) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{16} + return file_common_proto_rawDescGZIP(), []int{15} } type QueryMode int32 @@ -1146,11 +1075,11 @@ func (x QueryMode) String() string { } func (QueryMode) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[17].Descriptor() + return file_common_proto_enumTypes[16].Descriptor() } func (QueryMode) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[17] + return &file_common_proto_enumTypes[16] } func (x QueryMode) Number() protoreflect.EnumNumber { @@ -1159,7 +1088,7 @@ func (x QueryMode) Number() protoreflect.EnumNumber { // Deprecated: Use QueryMode.Descriptor instead. func (QueryMode) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{17} + return file_common_proto_rawDescGZIP(), []int{16} } type Timestamp struct { @@ -5758,6 +5687,15 @@ type LedgerLog struct { // purged while another stays kept; tagging the account as a whole would // over-skip mappings for transactions that touched the kept asset. PurgedVolumes []*TouchedVolume `protobuf:"bytes,4,rep,name=purged_volumes,json=purgedVolumes,proto3" json:"purged_volumes,omitempty"` + // Volumes (account+asset) whose persistent entry was newly created (never + // seen in the attribute store before) by THIS log. Ephemeral volumes appear + // in BOTH new_volumes AND purged_volumes for the same log: they were + // persisted briefly then evicted after commit. Transient volumes (never + // persisted) are excluded by construction. The usagebuilder derives the + // VolumeCount projection as sum(new_volumes) - sum(purged_volumes) across + // the audit chain; the checker verifies the derived counter against the + // live attribute store. + NewVolumes []*TouchedVolume `protobuf:"bytes,5,rep,name=new_volumes,json=newVolumes,proto3" json:"new_volumes,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5820,6 +5758,13 @@ func (x *LedgerLog) GetPurgedVolumes() []*TouchedVolume { return nil } +func (x *LedgerLog) GetNewVolumes() []*TouchedVolume { + if x != nil { + return x.NewVolumes + } + return nil +} + // TouchedVolume identifies a (ledger-local) volume cell — an account paired // with an asset. Used in transient/purged volume exclusion sets where the // indexer must distinguish per-asset state inside a multi-asset account. @@ -9907,7 +9852,6 @@ type QueryFilter struct { // *QueryFilter_LogBuiltinUint // *QueryFilter_AccountHasAsset // *QueryFilter_Reverted - // *QueryFilter_Audit Filter isQueryFilter_Filter `protobuf_oneof:"filter"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -10058,15 +10002,6 @@ func (x *QueryFilter) GetReverted() *RevertedCondition { return nil } -func (x *QueryFilter) GetAudit() *AuditCondition { - if x != nil { - if x, ok := x.Filter.(*QueryFilter_Audit); ok { - return x.Audit - } - } - return nil -} - type isQueryFilter_Filter interface { isQueryFilter_Filter() } @@ -10119,10 +10054,6 @@ type QueryFilter_Reverted struct { Reverted *RevertedCondition `protobuf:"bytes,12,opt,name=reverted,proto3,oneof"` } -type QueryFilter_Audit struct { - Audit *AuditCondition `protobuf:"bytes,13,opt,name=audit,proto3,oneof"` -} - func (*QueryFilter_Field) isQueryFilter_Filter() {} func (*QueryFilter_Address) isQueryFilter_Filter() {} @@ -10147,8 +10078,6 @@ func (*QueryFilter_AccountHasAsset) isQueryFilter_Filter() {} func (*QueryFilter_Reverted) isQueryFilter_Filter() {} -func (*QueryFilter_Audit) isQueryFilter_Filter() {} - // ReferenceCondition filters transactions by reference (exact match). type ReferenceCondition struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -10241,110 +10170,6 @@ func (x *RevertedCondition) GetValue() bool { return false } -// AuditCondition filters audit entries by a single audit field, mirroring -// BuiltinUintCondition's (field-enum × condition) shape. It is only meaningful -// for the audit list path: the audit compiler (query.CompileAuditFilter) accepts -// Audit/And/Or leaves and rejects every other QueryFilter variant with -// InvalidArgument, while the index compiler used for accounts/transactions/logs -// (query.Compile) never lists QueryFilter_Audit and rejects it there too. There -// is no QueryTarget dispatch for audit — ListAuditEntries calls -// CompileAuditFilter directly, so no QUERY_TARGET_AUDIT enum value is needed. -// -// Every exposed field is answerable from the readstore audit secondary index -// (EN-1339) — outcome, ledger, caller_subject, order_type, timestamp, -// proposal_id, log_seq — except AUDIT_FIELD_SEQUENCE, which is the audit-zone -// key itself and is served by bounding the entry scan. There is deliberately no -// scan-time predicate fallback: a field the index cannot answer is not exposed. -type AuditCondition struct { - state protoimpl.MessageState `protogen:"open.v1"` - Field AuditField `protobuf:"varint,1,opt,name=field,proto3,enum=common.AuditField" json:"field,omitempty"` - // Types that are valid to be assigned to Condition: - // - // *AuditCondition_StringCond - // *AuditCondition_UintCond - Condition isAuditCondition_Condition `protobuf_oneof:"condition"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AuditCondition) Reset() { - *x = AuditCondition{} - mi := &file_common_proto_msgTypes[126] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AuditCondition) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AuditCondition) ProtoMessage() {} - -func (x *AuditCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[126] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AuditCondition.ProtoReflect.Descriptor instead. -func (*AuditCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{126} -} - -func (x *AuditCondition) GetField() AuditField { - if x != nil { - return x.Field - } - return AuditField_AUDIT_FIELD_UNSPECIFIED -} - -func (x *AuditCondition) GetCondition() isAuditCondition_Condition { - if x != nil { - return x.Condition - } - return nil -} - -func (x *AuditCondition) GetStringCond() *StringCondition { - if x != nil { - if x, ok := x.Condition.(*AuditCondition_StringCond); ok { - return x.StringCond - } - } - return nil -} - -func (x *AuditCondition) GetUintCond() *UintCondition { - if x != nil { - if x, ok := x.Condition.(*AuditCondition_UintCond); ok { - return x.UintCond - } - } - return nil -} - -type isAuditCondition_Condition interface { - isAuditCondition_Condition() -} - -type AuditCondition_StringCond struct { - StringCond *StringCondition `protobuf:"bytes,2,opt,name=string_cond,json=stringCond,proto3,oneof"` -} - -type AuditCondition_UintCond struct { - UintCond *UintCondition `protobuf:"bytes,3,opt,name=uint_cond,json=uintCond,proto3,oneof"` -} - -func (*AuditCondition_StringCond) isAuditCondition_Condition() {} - -func (*AuditCondition_UintCond) isAuditCondition_Condition() {} - // LedgerCondition filters logs by ledger name (exact match). type LedgerCondition struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -10355,7 +10180,7 @@ type LedgerCondition struct { func (x *LedgerCondition) Reset() { *x = LedgerCondition{} - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10367,7 +10192,7 @@ func (x *LedgerCondition) String() string { func (*LedgerCondition) ProtoMessage() {} func (x *LedgerCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10380,7 +10205,7 @@ func (x *LedgerCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerCondition.ProtoReflect.Descriptor instead. func (*LedgerCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{127} + return file_common_proto_rawDescGZIP(), []int{126} } func (x *LedgerCondition) GetCond() *StringCondition { @@ -10400,7 +10225,7 @@ type LogIdCondition struct { func (x *LogIdCondition) Reset() { *x = LogIdCondition{} - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10412,7 +10237,7 @@ func (x *LogIdCondition) String() string { func (*LogIdCondition) ProtoMessage() {} func (x *LogIdCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10425,7 +10250,7 @@ func (x *LogIdCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogIdCondition.ProtoReflect.Descriptor instead. func (*LogIdCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{128} + return file_common_proto_rawDescGZIP(), []int{127} } func (x *LogIdCondition) GetCond() *UintCondition { @@ -10435,7 +10260,7 @@ func (x *LogIdCondition) GetCond() *UintCondition { return nil } -// BuiltinUintCondition filters transactions by a built-in uint64 field (id, timestamp, inserted_at, or reverted_at). +// BuiltinUintCondition filters transactions by a built-in uint64 field (id, timestamp, or inserted_at). type BuiltinUintCondition struct { state protoimpl.MessageState `protogen:"open.v1"` Field TransactionBuiltinIndex `protobuf:"varint,1,opt,name=field,proto3,enum=common.TransactionBuiltinIndex" json:"field,omitempty"` @@ -10446,7 +10271,7 @@ type BuiltinUintCondition struct { func (x *BuiltinUintCondition) Reset() { *x = BuiltinUintCondition{} - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10458,7 +10283,7 @@ func (x *BuiltinUintCondition) String() string { func (*BuiltinUintCondition) ProtoMessage() {} func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10471,7 +10296,7 @@ func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BuiltinUintCondition.ProtoReflect.Descriptor instead. func (*BuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{129} + return file_common_proto_rawDescGZIP(), []int{128} } func (x *BuiltinUintCondition) GetField() TransactionBuiltinIndex { @@ -10499,7 +10324,7 @@ type LogBuiltinUintCondition struct { func (x *LogBuiltinUintCondition) Reset() { *x = LogBuiltinUintCondition{} - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10511,7 +10336,7 @@ func (x *LogBuiltinUintCondition) String() string { func (*LogBuiltinUintCondition) ProtoMessage() {} func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10524,7 +10349,7 @@ func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogBuiltinUintCondition.ProtoReflect.Descriptor instead. func (*LogBuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{130} + return file_common_proto_rawDescGZIP(), []int{129} } func (x *LogBuiltinUintCondition) GetField() LogBuiltinIndex { @@ -10555,7 +10380,7 @@ type AccountHasAssetCondition struct { func (x *AccountHasAssetCondition) Reset() { *x = AccountHasAssetCondition{} - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10567,7 +10392,7 @@ func (x *AccountHasAssetCondition) String() string { func (*AccountHasAssetCondition) ProtoMessage() {} func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10580,7 +10405,7 @@ func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountHasAssetCondition.ProtoReflect.Descriptor instead. func (*AccountHasAssetCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{131} + return file_common_proto_rawDescGZIP(), []int{130} } func (x *AccountHasAssetCondition) GetAssetBase() string { @@ -10606,7 +10431,7 @@ type AndFilter struct { func (x *AndFilter) Reset() { *x = AndFilter{} - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10618,7 +10443,7 @@ func (x *AndFilter) String() string { func (*AndFilter) ProtoMessage() {} func (x *AndFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10631,7 +10456,7 @@ func (x *AndFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use AndFilter.ProtoReflect.Descriptor instead. func (*AndFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{132} + return file_common_proto_rawDescGZIP(), []int{131} } func (x *AndFilter) GetFilters() []*QueryFilter { @@ -10650,7 +10475,7 @@ type OrFilter struct { func (x *OrFilter) Reset() { *x = OrFilter{} - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10662,7 +10487,7 @@ func (x *OrFilter) String() string { func (*OrFilter) ProtoMessage() {} func (x *OrFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10675,7 +10500,7 @@ func (x *OrFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use OrFilter.ProtoReflect.Descriptor instead. func (*OrFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{133} + return file_common_proto_rawDescGZIP(), []int{132} } func (x *OrFilter) GetFilters() []*QueryFilter { @@ -10694,7 +10519,7 @@ type NotFilter struct { func (x *NotFilter) Reset() { *x = NotFilter{} - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10706,7 +10531,7 @@ func (x *NotFilter) String() string { func (*NotFilter) ProtoMessage() {} func (x *NotFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10719,7 +10544,7 @@ func (x *NotFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use NotFilter.ProtoReflect.Descriptor instead. func (*NotFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{134} + return file_common_proto_rawDescGZIP(), []int{133} } func (x *NotFilter) GetFilter() *QueryFilter { @@ -10741,7 +10566,7 @@ type FieldRef struct { func (x *FieldRef) Reset() { *x = FieldRef{} - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10753,7 +10578,7 @@ func (x *FieldRef) String() string { func (*FieldRef) ProtoMessage() {} func (x *FieldRef) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10766,7 +10591,7 @@ func (x *FieldRef) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldRef.ProtoReflect.Descriptor instead. func (*FieldRef) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{135} + return file_common_proto_rawDescGZIP(), []int{134} } func (x *FieldRef) GetMetadata() string { @@ -10794,7 +10619,7 @@ type FieldCondition struct { func (x *FieldCondition) Reset() { *x = FieldCondition{} - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10806,7 +10631,7 @@ func (x *FieldCondition) String() string { func (*FieldCondition) ProtoMessage() {} func (x *FieldCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10819,7 +10644,7 @@ func (x *FieldCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldCondition.ProtoReflect.Descriptor instead. func (*FieldCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{136} + return file_common_proto_rawDescGZIP(), []int{135} } func (x *FieldCondition) GetField() *FieldRef { @@ -10928,7 +10753,7 @@ type StringCondition struct { func (x *StringCondition) Reset() { *x = StringCondition{} - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10940,7 +10765,7 @@ func (x *StringCondition) String() string { func (*StringCondition) ProtoMessage() {} func (x *StringCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10953,7 +10778,7 @@ func (x *StringCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use StringCondition.ProtoReflect.Descriptor instead. func (*StringCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{137} + return file_common_proto_rawDescGZIP(), []int{136} } func (x *StringCondition) GetValue() isStringCondition_Value { @@ -11011,7 +10836,7 @@ type IntCondition struct { func (x *IntCondition) Reset() { *x = IntCondition{} - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11023,7 +10848,7 @@ func (x *IntCondition) String() string { func (*IntCondition) ProtoMessage() {} func (x *IntCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11036,7 +10861,7 @@ func (x *IntCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use IntCondition.ProtoReflect.Descriptor instead. func (*IntCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{138} + return file_common_proto_rawDescGZIP(), []int{137} } func (x *IntCondition) GetMin() int64 { @@ -11095,7 +10920,7 @@ type UintCondition struct { func (x *UintCondition) Reset() { *x = UintCondition{} - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11107,7 +10932,7 @@ func (x *UintCondition) String() string { func (*UintCondition) ProtoMessage() {} func (x *UintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11120,7 +10945,7 @@ func (x *UintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use UintCondition.ProtoReflect.Descriptor instead. func (*UintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{139} + return file_common_proto_rawDescGZIP(), []int{138} } func (x *UintCondition) GetMin() uint64 { @@ -11178,7 +11003,7 @@ type BoolCondition struct { func (x *BoolCondition) Reset() { *x = BoolCondition{} - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11190,7 +11015,7 @@ func (x *BoolCondition) String() string { func (*BoolCondition) ProtoMessage() {} func (x *BoolCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11203,7 +11028,7 @@ func (x *BoolCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BoolCondition.ProtoReflect.Descriptor instead. func (*BoolCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{140} + return file_common_proto_rawDescGZIP(), []int{139} } func (x *BoolCondition) GetValue() isBoolCondition_Value { @@ -11256,7 +11081,7 @@ type ExistsCondition struct { func (x *ExistsCondition) Reset() { *x = ExistsCondition{} - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11268,7 +11093,7 @@ func (x *ExistsCondition) String() string { func (*ExistsCondition) ProtoMessage() {} func (x *ExistsCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11281,7 +11106,7 @@ func (x *ExistsCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use ExistsCondition.ProtoReflect.Descriptor instead. func (*ExistsCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{141} + return file_common_proto_rawDescGZIP(), []int{140} } func (x *ExistsCondition) GetIncludeNull() bool { @@ -11307,7 +11132,7 @@ type AddressMatch struct { func (x *AddressMatch) Reset() { *x = AddressMatch{} - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11319,7 +11144,7 @@ func (x *AddressMatch) String() string { func (*AddressMatch) ProtoMessage() {} func (x *AddressMatch) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11332,7 +11157,7 @@ func (x *AddressMatch) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressMatch.ProtoReflect.Descriptor instead. func (*AddressMatch) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{142} + return file_common_proto_rawDescGZIP(), []int{141} } func (x *AddressMatch) GetMatch() isAddressMatch_Match { @@ -11428,7 +11253,7 @@ type PreparedQuery struct { func (x *PreparedQuery) Reset() { *x = PreparedQuery{} - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11440,7 +11265,7 @@ func (x *PreparedQuery) String() string { func (*PreparedQuery) ProtoMessage() {} func (x *PreparedQuery) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11453,7 +11278,7 @@ func (x *PreparedQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQuery.ProtoReflect.Descriptor instead. func (*PreparedQuery) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{143} + return file_common_proto_rawDescGZIP(), []int{142} } func (x *PreparedQuery) GetName() string { @@ -11489,7 +11314,7 @@ type AggregatedVolume struct { func (x *AggregatedVolume) Reset() { *x = AggregatedVolume{} - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11501,7 +11326,7 @@ func (x *AggregatedVolume) String() string { func (*AggregatedVolume) ProtoMessage() {} func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11514,7 +11339,7 @@ func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregatedVolume.ProtoReflect.Descriptor instead. func (*AggregatedVolume) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{144} + return file_common_proto_rawDescGZIP(), []int{143} } func (x *AggregatedVolume) GetAsset() string { @@ -11549,7 +11374,7 @@ type AggregateResult struct { func (x *AggregateResult) Reset() { *x = AggregateResult{} - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11561,7 +11386,7 @@ func (x *AggregateResult) String() string { func (*AggregateResult) ProtoMessage() {} func (x *AggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11574,7 +11399,7 @@ func (x *AggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregateResult.ProtoReflect.Descriptor instead. func (*AggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{145} + return file_common_proto_rawDescGZIP(), []int{144} } func (x *AggregateResult) GetVolumes() []*AggregatedVolume { @@ -11602,7 +11427,7 @@ type GroupedAggregateResult struct { func (x *GroupedAggregateResult) Reset() { *x = GroupedAggregateResult{} - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11614,7 +11439,7 @@ func (x *GroupedAggregateResult) String() string { func (*GroupedAggregateResult) ProtoMessage() {} func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11627,7 +11452,7 @@ func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use GroupedAggregateResult.ProtoReflect.Descriptor instead. func (*GroupedAggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{146} + return file_common_proto_rawDescGZIP(), []int{145} } func (x *GroupedAggregateResult) GetPrefix() string { @@ -11659,7 +11484,7 @@ type PreparedQueryCursor struct { func (x *PreparedQueryCursor) Reset() { *x = PreparedQueryCursor{} - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11671,7 +11496,7 @@ func (x *PreparedQueryCursor) String() string { func (*PreparedQueryCursor) ProtoMessage() {} func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11684,7 +11509,7 @@ func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQueryCursor.ProtoReflect.Descriptor instead. func (*PreparedQueryCursor) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{147} + return file_common_proto_rawDescGZIP(), []int{146} } func (x *PreparedQueryCursor) GetPageSize() uint32 { @@ -11747,7 +11572,7 @@ type LedgerStats struct { func (x *LedgerStats) Reset() { *x = LedgerStats{} - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11759,7 +11584,7 @@ func (x *LedgerStats) String() string { func (*LedgerStats) ProtoMessage() {} func (x *LedgerStats) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11772,7 +11597,7 @@ func (x *LedgerStats) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerStats.ProtoReflect.Descriptor instead. func (*LedgerStats) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{148} + return file_common_proto_rawDescGZIP(), []int{147} } func (x *LedgerStats) GetTransactionCount() uint64 { @@ -11853,7 +11678,7 @@ type PersistedConfig struct { func (x *PersistedConfig) Reset() { *x = PersistedConfig{} - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11865,7 +11690,7 @@ func (x *PersistedConfig) String() string { func (*PersistedConfig) ProtoMessage() {} func (x *PersistedConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11878,7 +11703,7 @@ func (x *PersistedConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PersistedConfig.ProtoReflect.Descriptor instead. func (*PersistedConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{149} + return file_common_proto_rawDescGZIP(), []int{148} } func (x *PersistedConfig) GetNodeId() uint64 { @@ -11936,7 +11761,7 @@ type CallerIdentity struct { func (x *CallerIdentity) Reset() { *x = CallerIdentity{} - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11948,7 +11773,7 @@ func (x *CallerIdentity) String() string { func (*CallerIdentity) ProtoMessage() {} func (x *CallerIdentity) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11961,7 +11786,7 @@ func (x *CallerIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerIdentity.ProtoReflect.Descriptor instead. func (*CallerIdentity) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{150} + return file_common_proto_rawDescGZIP(), []int{149} } func (x *CallerIdentity) GetSubject() string { @@ -12048,7 +11873,7 @@ type CallerSnapshot struct { func (x *CallerSnapshot) Reset() { *x = CallerSnapshot{} - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12060,7 +11885,7 @@ func (x *CallerSnapshot) String() string { func (*CallerSnapshot) ProtoMessage() {} func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12073,7 +11898,7 @@ func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerSnapshot.ProtoReflect.Descriptor instead. func (*CallerSnapshot) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{151} + return file_common_proto_rawDescGZIP(), []int{150} } func (x *CallerSnapshot) GetIdentity() *CallerIdentity { @@ -12111,7 +11936,7 @@ type S3StorageConfig struct { func (x *S3StorageConfig) Reset() { *x = S3StorageConfig{} - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12123,7 +11948,7 @@ func (x *S3StorageConfig) String() string { func (*S3StorageConfig) ProtoMessage() {} func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12136,7 +11961,7 @@ func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use S3StorageConfig.ProtoReflect.Descriptor instead. func (*S3StorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{152} + return file_common_proto_rawDescGZIP(), []int{151} } func (x *S3StorageConfig) GetBucket() string { @@ -12187,7 +12012,7 @@ type AzureStorageConfig struct { func (x *AzureStorageConfig) Reset() { *x = AzureStorageConfig{} - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12199,7 +12024,7 @@ func (x *AzureStorageConfig) String() string { func (*AzureStorageConfig) ProtoMessage() {} func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12212,7 +12037,7 @@ func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureStorageConfig.ProtoReflect.Descriptor instead. func (*AzureStorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{153} + return file_common_proto_rawDescGZIP(), []int{152} } func (x *AzureStorageConfig) GetAccountName() string { @@ -12259,7 +12084,7 @@ type BackupStorage struct { func (x *BackupStorage) Reset() { *x = BackupStorage{} - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12271,7 +12096,7 @@ func (x *BackupStorage) String() string { func (*BackupStorage) ProtoMessage() {} func (x *BackupStorage) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12284,7 +12109,7 @@ func (x *BackupStorage) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupStorage.ProtoReflect.Descriptor instead. func (*BackupStorage) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{154} + return file_common_proto_rawDescGZIP(), []int{153} } func (x *BackupStorage) GetProvider() isBackupStorage_Provider { @@ -12347,7 +12172,7 @@ type ReadOptions struct { func (x *ReadOptions) Reset() { *x = ReadOptions{} - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12359,7 +12184,7 @@ func (x *ReadOptions) String() string { func (*ReadOptions) ProtoMessage() {} func (x *ReadOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12372,7 +12197,7 @@ func (x *ReadOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadOptions.ProtoReflect.Descriptor instead. func (*ReadOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{155} + return file_common_proto_rawDescGZIP(), []int{154} } func (x *ReadOptions) GetCheckpointId() uint64 { @@ -12424,7 +12249,7 @@ type ListOptions struct { func (x *ListOptions) Reset() { *x = ListOptions{} - mi := &file_common_proto_msgTypes[156] + mi := &file_common_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12436,7 +12261,7 @@ func (x *ListOptions) String() string { func (*ListOptions) ProtoMessage() {} func (x *ListOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[156] + mi := &file_common_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12449,7 +12274,7 @@ func (x *ListOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOptions.ProtoReflect.Descriptor instead. func (*ListOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{156} + return file_common_proto_rawDescGZIP(), []int{155} } func (x *ListOptions) GetRead() *ReadOptions { @@ -12849,12 +12674,14 @@ const file_common_proto_rawDesc = "" + "\x0eApplyLedgerLog\x12\x1f\n" + "\vledger_name\x18\x01 \x01(\tR\n" + "ledgerName\x12#\n" + - "\x03log\x18\x02 \x01(\v2\x11.common.LedgerLogR\x03log\"\xae\x01\n" + + "\x03log\x18\x02 \x01(\v2\x11.common.LedgerLogR\x03log\"\xe6\x01\n" + "\tLedgerLog\x12,\n" + "\x04data\x18\x01 \x01(\v2\x18.common.LedgerLogPayloadR\x04data\x12%\n" + "\x04date\x18\x02 \x01(\v2\x11.common.TimestampR\x04date\x12\x0e\n" + "\x02id\x18\x03 \x01(\x06R\x02id\x12<\n" + - "\x0epurged_volumes\x18\x04 \x03(\v2\x15.common.TouchedVolumeR\rpurgedVolumes\"?\n" + + "\x0epurged_volumes\x18\x04 \x03(\v2\x15.common.TouchedVolumeR\rpurgedVolumes\x126\n" + + "\vnew_volumes\x18\x05 \x03(\v2\x15.common.TouchedVolumeR\n" + + "newVolumes\"?\n" + "\rTouchedVolume\x12\x18\n" + "\aaccount\x18\x01 \x01(\tR\aaccount\x12\x14\n" + "\x05asset\x18\x02 \x01(\tR\x05asset\"\xc4\a\n" + @@ -13141,7 +12968,7 @@ const file_common_proto_rawDesc = "" + "\x15RemovedAccountTypeLog\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\"k\n" + " UpdatedDefaultEnforcementModeLog\x12G\n" + - "\x10enforcement_mode\x18\x01 \x01(\x0e2\x1c.common.ChartEnforcementModeR\x0fenforcementMode\"\xd4\x05\n" + + "\x10enforcement_mode\x18\x01 \x01(\x0e2\x1c.common.ChartEnforcementModeR\x0fenforcementMode\"\xa4\x05\n" + "\vQueryFilter\x12.\n" + "\x05field\x18\x01 \x01(\v2\x16.common.FieldConditionH\x00R\x05field\x120\n" + "\aaddress\x18\x02 \x01(\v2\x14.common.AddressMatchH\x00R\aaddress\x12%\n" + @@ -13155,19 +12982,12 @@ const file_common_proto_rawDesc = "" + "\x10log_builtin_uint\x18\n" + " \x01(\v2\x1f.common.LogBuiltinUintConditionH\x00R\x0elogBuiltinUint\x12N\n" + "\x11account_has_asset\x18\v \x01(\v2 .common.AccountHasAssetConditionH\x00R\x0faccountHasAsset\x127\n" + - "\breverted\x18\f \x01(\v2\x19.common.RevertedConditionH\x00R\breverted\x12.\n" + - "\x05audit\x18\r \x01(\v2\x16.common.AuditConditionH\x00R\x05auditB\b\n" + + "\breverted\x18\f \x01(\v2\x19.common.RevertedConditionH\x00R\brevertedB\b\n" + "\x06filter\"A\n" + "\x12ReferenceCondition\x12+\n" + "\x04cond\x18\x01 \x01(\v2\x17.common.StringConditionR\x04cond\")\n" + "\x11RevertedCondition\x12\x14\n" + - "\x05value\x18\x01 \x01(\bR\x05value\"\xb9\x01\n" + - "\x0eAuditCondition\x12(\n" + - "\x05field\x18\x01 \x01(\x0e2\x12.common.AuditFieldR\x05field\x12:\n" + - "\vstring_cond\x18\x02 \x01(\v2\x17.common.StringConditionH\x00R\n" + - "stringCond\x124\n" + - "\tuint_cond\x18\x03 \x01(\v2\x15.common.UintConditionH\x00R\buintCondB\v\n" + - "\tcondition\">\n" + + "\x05value\x18\x01 \x01(\bR\x05value\">\n" + "\x0fLedgerCondition\x12+\n" + "\x04cond\x18\x01 \x01(\v2\x17.common.StringConditionR\x04cond\";\n" + "\x0eLogIdCondition\x12)\n" + @@ -13443,18 +13263,7 @@ const file_common_proto_rawDesc = "" + "\x16AccountTypePersistence\x12\x17\n" + "\x13ACCOUNT_TYPE_NORMAL\x10\x00\x12\x1a\n" + "\x16ACCOUNT_TYPE_EPHEMERAL\x10\x01\x12\x1a\n" + - "\x16ACCOUNT_TYPE_TRANSIENT\x10\x02*\x86\x02\n" + - "\n" + - "AuditField\x12\x1b\n" + - "\x17AUDIT_FIELD_UNSPECIFIED\x10\x00\x12\x18\n" + - "\x14AUDIT_FIELD_SEQUENCE\x10\x01\x12\x1b\n" + - "\x17AUDIT_FIELD_PROPOSAL_ID\x10\x02\x12\x19\n" + - "\x15AUDIT_FIELD_TIMESTAMP\x10\x03\x12\x1c\n" + - "\x18AUDIT_FIELD_LOG_SEQUENCE\x10\x04\x12\x17\n" + - "\x13AUDIT_FIELD_OUTCOME\x10\x05\x12\x1e\n" + - "\x1aAUDIT_FIELD_CALLER_SUBJECT\x10\x06\x12\x16\n" + - "\x12AUDIT_FIELD_LEDGER\x10\a\x12\x1a\n" + - "\x16AUDIT_FIELD_ORDER_TYPE\x10\b*Z\n" + + "\x16ACCOUNT_TYPE_TRANSIENT\x10\x02*Z\n" + "\vAddressRole\x12\x14\n" + "\x10ADDRESS_ROLE_ANY\x10\x00\x12\x17\n" + "\x13ADDRESS_ROLE_SOURCE\x10\x01\x12\x1c\n" + @@ -13479,8 +13288,8 @@ func file_common_proto_rawDescGZIP() []byte { return file_common_proto_rawDescData } -var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 18) -var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 177) +var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 17) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 176) var file_common_proto_goTypes = []any{ (TargetType)(0), // 0: common.TargetType (MetadataType)(0), // 1: common.MetadataType @@ -13496,462 +13305,457 @@ var file_common_proto_goTypes = []any{ (ErrorReason)(0), // 11: common.ErrorReason (ChartEnforcementMode)(0), // 12: common.ChartEnforcementMode (AccountTypePersistence)(0), // 13: common.AccountTypePersistence - (AuditField)(0), // 14: common.AuditField - (AddressRole)(0), // 15: common.AddressRole - (QueryTarget)(0), // 16: common.QueryTarget - (QueryMode)(0), // 17: common.QueryMode - (*Timestamp)(nil), // 18: common.Timestamp - (*NullValue)(nil), // 19: common.NullValue - (*MetadataValue)(nil), // 20: common.MetadataValue - (*MetadataMap)(nil), // 21: common.MetadataMap - (*ParameterValue)(nil), // 22: common.ParameterValue - (*Uint256)(nil), // 23: common.Uint256 - (*Posting)(nil), // 24: common.Posting - (*Transaction)(nil), // 25: common.Transaction - (*Script)(nil), // 26: common.Script - (*Volumes)(nil), // 27: common.Volumes - (*VolumesWithBalance)(nil), // 28: common.VolumesWithBalance - (*VolumesByAssets)(nil), // 29: common.VolumesByAssets - (*PostCommitVolumes)(nil), // 30: common.PostCommitVolumes - (*Account)(nil), // 31: common.Account - (*TargetAccount)(nil), // 32: common.TargetAccount - (*Target)(nil), // 33: common.Target - (*MetadataFieldSchema)(nil), // 34: common.MetadataFieldSchema - (*MetadataSchema)(nil), // 35: common.MetadataSchema - (*SetMetadataFieldTypeCommand)(nil), // 36: common.SetMetadataFieldTypeCommand - (*MetadataIndexID)(nil), // 37: common.MetadataIndexID - (*IndexID)(nil), // 38: common.IndexID - (*Index)(nil), // 39: common.Index - (*Idempotency)(nil), // 40: common.Idempotency - (*IdempotencyEntry)(nil), // 41: common.IdempotencyEntry - (*Log)(nil), // 42: common.Log - (*LogPayload)(nil), // 43: common.LogPayload - (*PromotedLedgerLog)(nil), // 44: common.PromotedLedgerLog - (*RegisteredSigningKeyLog)(nil), // 45: common.RegisteredSigningKeyLog - (*RevokedSigningKeyLog)(nil), // 46: common.RevokedSigningKeyLog - (*SigningKey)(nil), // 47: common.SigningKey - (*SetSigningConfigLog)(nil), // 48: common.SetSigningConfigLog - (*AddedEventsSinkLog)(nil), // 49: common.AddedEventsSinkLog - (*RemovedEventsSinkLog)(nil), // 50: common.RemovedEventsSinkLog - (*SetMaintenanceModeLog)(nil), // 51: common.SetMaintenanceModeLog - (*BloomTypeConfig)(nil), // 52: common.BloomTypeConfig - (*ClusterConfig)(nil), // 53: common.ClusterConfig - (*PersistedClusterState)(nil), // 54: common.PersistedClusterState - (*SetChapterScheduleLog)(nil), // 55: common.SetChapterScheduleLog - (*DeletedChapterScheduleLog)(nil), // 56: common.DeletedChapterScheduleLog - (*CreatedPreparedQueryLog)(nil), // 57: common.CreatedPreparedQueryLog - (*UpdatedPreparedQueryLog)(nil), // 58: common.UpdatedPreparedQueryLog - (*DeletedPreparedQueryLog)(nil), // 59: common.DeletedPreparedQueryLog - (*SavedLedgerMetadataLog)(nil), // 60: common.SavedLedgerMetadataLog - (*DeletedLedgerMetadataLog)(nil), // 61: common.DeletedLedgerMetadataLog - (*NumscriptInfo)(nil), // 62: common.NumscriptInfo - (*SavedNumscriptLog)(nil), // 63: common.SavedNumscriptLog - (*DeletedNumscriptLog)(nil), // 64: common.DeletedNumscriptLog - (*TemplateUsage)(nil), // 65: common.TemplateUsage - (*SetQueryCheckpointScheduleLog)(nil), // 66: common.SetQueryCheckpointScheduleLog - (*DeletedQueryCheckpointScheduleLog)(nil), // 67: common.DeletedQueryCheckpointScheduleLog - (*CreatedQueryCheckpointLog)(nil), // 68: common.CreatedQueryCheckpointLog - (*DeletedQueryCheckpointLog)(nil), // 69: common.DeletedQueryCheckpointLog - (*SinkConfig)(nil), // 70: common.SinkConfig - (*SinkStatus)(nil), // 71: common.SinkStatus - (*SinkError)(nil), // 72: common.SinkError - (*NatsSinkConfig)(nil), // 73: common.NatsSinkConfig - (*ClickHouseSinkConfig)(nil), // 74: common.ClickHouseSinkConfig - (*KafkaSinkConfig)(nil), // 75: common.KafkaSinkConfig - (*HttpSinkConfig)(nil), // 76: common.HttpSinkConfig - (*DatabricksSinkConfig)(nil), // 77: common.DatabricksSinkConfig - (*DatabricksOAuthM2M)(nil), // 78: common.DatabricksOAuthM2M - (*CreatedLedgerLog)(nil), // 79: common.CreatedLedgerLog - (*DeletedLedgerLog)(nil), // 80: common.DeletedLedgerLog - (*ApplyLedgerLog)(nil), // 81: common.ApplyLedgerLog - (*LedgerLog)(nil), // 82: common.LedgerLog - (*TouchedVolume)(nil), // 83: common.TouchedVolume - (*LedgerLogPayload)(nil), // 84: common.LedgerLogPayload - (*CreatedIndexLog)(nil), // 85: common.CreatedIndexLog - (*DroppedIndexLog)(nil), // 86: common.DroppedIndexLog - (*FilledGapLog)(nil), // 87: common.FilledGapLog - (*CreatedTransaction)(nil), // 88: common.CreatedTransaction - (*RevertedTransaction)(nil), // 89: common.RevertedTransaction - (*SavedMetadata)(nil), // 90: common.SavedMetadata - (*DeletedMetadata)(nil), // 91: common.DeletedMetadata - (*SetMetadataFieldTypeLog)(nil), // 92: common.SetMetadataFieldTypeLog - (*RemovedMetadataFieldTypeLog)(nil), // 93: common.RemovedMetadataFieldTypeLog - (*Chapter)(nil), // 94: common.Chapter - (*ClosedChapterLog)(nil), // 95: common.ClosedChapterLog - (*SealedChapterLog)(nil), // 96: common.SealedChapterLog - (*ArchivedChapterLog)(nil), // 97: common.ArchivedChapterLog - (*ConfirmedArchiveChapterLog)(nil), // 98: common.ConfirmedArchiveChapterLog - (*MirrorSourceConfig)(nil), // 99: common.MirrorSourceConfig - (*MirrorRewriteRule)(nil), // 100: common.MirrorRewriteRule - (*CreatedTransactionRule)(nil), // 101: common.CreatedTransactionRule - (*RevertedTransactionRule)(nil), // 102: common.RevertedTransactionRule - (*SavedMetadataRule)(nil), // 103: common.SavedMetadataRule - (*DeletedMetadataRule)(nil), // 104: common.DeletedMetadataRule - (*AnyVariantRule)(nil), // 105: common.AnyVariantRule - (*CreatedTransactionAction)(nil), // 106: common.CreatedTransactionAction - (*RevertedTransactionAction)(nil), // 107: common.RevertedTransactionAction - (*SavedMetadataAction)(nil), // 108: common.SavedMetadataAction - (*DeletedMetadataAction)(nil), // 109: common.DeletedMetadataAction - (*AnyVariantAction)(nil), // 110: common.AnyVariantAction - (*RewriteAddressAction)(nil), // 111: common.RewriteAddressAction - (*SetMetadataAction)(nil), // 112: common.SetMetadataAction - (*DeleteMetadataAction)(nil), // 113: common.DeleteMetadataAction - (*SetAccountMetadataAction)(nil), // 114: common.SetAccountMetadataAction - (*DeleteAccountMetadataAction)(nil), // 115: common.DeleteAccountMetadataAction - (*SetAccountMetadataFromAddressAction)(nil), // 116: common.SetAccountMetadataFromAddressAction - (*SetAccountMetadataFromAddressReplacement)(nil), // 117: common.SetAccountMetadataFromAddressReplacement - (*DropAction)(nil), // 118: common.DropAction - (*HttpMirrorSourceConfig)(nil), // 119: common.HttpMirrorSourceConfig - (*OAuth2ClientCredentials)(nil), // 120: common.OAuth2ClientCredentials - (*PostgresMirrorSourceConfig)(nil), // 121: common.PostgresMirrorSourceConfig - (*PostgresAwsIamAuth)(nil), // 122: common.PostgresAwsIamAuth - (*MirrorSyncError)(nil), // 123: common.MirrorSyncError - (*MirrorSyncProgress)(nil), // 124: common.MirrorSyncProgress - (*LedgerInfo)(nil), // 125: common.LedgerInfo - (*SaveMetadataCommand)(nil), // 126: common.SaveMetadataCommand - (*DeleteMetadataCommand)(nil), // 127: common.DeleteMetadataCommand - (*TransactionState)(nil), // 128: common.TransactionState - (*IdempotencyKeyValue)(nil), // 129: common.IdempotencyKeyValue - (*IdempotencyFailure)(nil), // 130: common.IdempotencyFailure - (*TransactionReferenceValue)(nil), // 131: common.TransactionReferenceValue - (*NumscriptVersionValue)(nil), // 132: common.NumscriptVersionValue - (*SegmentType)(nil), // 133: common.SegmentType - (*UUIDConstraint)(nil), // 134: common.UUIDConstraint - (*Uint64Constraint)(nil), // 135: common.Uint64Constraint - (*BytesConstraint)(nil), // 136: common.BytesConstraint - (*AccountType)(nil), // 137: common.AccountType - (*AddedAccountTypeLog)(nil), // 138: common.AddedAccountTypeLog - (*RemovedAccountTypeLog)(nil), // 139: common.RemovedAccountTypeLog - (*UpdatedDefaultEnforcementModeLog)(nil), // 140: common.UpdatedDefaultEnforcementModeLog - (*QueryFilter)(nil), // 141: common.QueryFilter - (*ReferenceCondition)(nil), // 142: common.ReferenceCondition - (*RevertedCondition)(nil), // 143: common.RevertedCondition - (*AuditCondition)(nil), // 144: common.AuditCondition - (*LedgerCondition)(nil), // 145: common.LedgerCondition - (*LogIdCondition)(nil), // 146: common.LogIdCondition - (*BuiltinUintCondition)(nil), // 147: common.BuiltinUintCondition - (*LogBuiltinUintCondition)(nil), // 148: common.LogBuiltinUintCondition - (*AccountHasAssetCondition)(nil), // 149: common.AccountHasAssetCondition - (*AndFilter)(nil), // 150: common.AndFilter - (*OrFilter)(nil), // 151: common.OrFilter - (*NotFilter)(nil), // 152: common.NotFilter - (*FieldRef)(nil), // 153: common.FieldRef - (*FieldCondition)(nil), // 154: common.FieldCondition - (*StringCondition)(nil), // 155: common.StringCondition - (*IntCondition)(nil), // 156: common.IntCondition - (*UintCondition)(nil), // 157: common.UintCondition - (*BoolCondition)(nil), // 158: common.BoolCondition - (*ExistsCondition)(nil), // 159: common.ExistsCondition - (*AddressMatch)(nil), // 160: common.AddressMatch - (*PreparedQuery)(nil), // 161: common.PreparedQuery - (*AggregatedVolume)(nil), // 162: common.AggregatedVolume - (*AggregateResult)(nil), // 163: common.AggregateResult - (*GroupedAggregateResult)(nil), // 164: common.GroupedAggregateResult - (*PreparedQueryCursor)(nil), // 165: common.PreparedQueryCursor - (*LedgerStats)(nil), // 166: common.LedgerStats - (*PersistedConfig)(nil), // 167: common.PersistedConfig - (*CallerIdentity)(nil), // 168: common.CallerIdentity - (*CallerSnapshot)(nil), // 169: common.CallerSnapshot - (*S3StorageConfig)(nil), // 170: common.S3StorageConfig - (*AzureStorageConfig)(nil), // 171: common.AzureStorageConfig - (*BackupStorage)(nil), // 172: common.BackupStorage - (*ReadOptions)(nil), // 173: common.ReadOptions - (*ListOptions)(nil), // 174: common.ListOptions - nil, // 175: common.MetadataMap.ValuesEntry - nil, // 176: common.Transaction.MetadataEntry - nil, // 177: common.Script.VarsEntry - nil, // 178: common.VolumesByAssets.VolumesEntry - nil, // 179: common.PostCommitVolumes.VolumesByAccountEntry - nil, // 180: common.Account.MetadataEntry - nil, // 181: common.Account.VolumesEntry - nil, // 182: common.MetadataSchema.AccountFieldsEntry - nil, // 183: common.MetadataSchema.TransactionFieldsEntry - nil, // 184: common.MetadataSchema.LedgerFieldsEntry - nil, // 185: common.SavedLedgerMetadataLog.MetadataEntry - nil, // 186: common.CreatedLedgerLog.AccountTypesEntry - nil, // 187: common.CreatedTransaction.AccountMetadataEntry - nil, // 188: common.SavedMetadata.MetadataEntry - nil, // 189: common.LedgerInfo.AccountTypesEntry - nil, // 190: common.LedgerInfo.MetadataEntry - nil, // 191: common.SaveMetadataCommand.MetadataEntry - nil, // 192: common.TransactionState.MetadataEntry - nil, // 193: common.IdempotencyFailure.MetadataEntry - nil, // 194: common.AccountType.SegmentTypesEntry - (*signaturepb.SignedLog)(nil), // 195: signature.SignedLog + (AddressRole)(0), // 14: common.AddressRole + (QueryTarget)(0), // 15: common.QueryTarget + (QueryMode)(0), // 16: common.QueryMode + (*Timestamp)(nil), // 17: common.Timestamp + (*NullValue)(nil), // 18: common.NullValue + (*MetadataValue)(nil), // 19: common.MetadataValue + (*MetadataMap)(nil), // 20: common.MetadataMap + (*ParameterValue)(nil), // 21: common.ParameterValue + (*Uint256)(nil), // 22: common.Uint256 + (*Posting)(nil), // 23: common.Posting + (*Transaction)(nil), // 24: common.Transaction + (*Script)(nil), // 25: common.Script + (*Volumes)(nil), // 26: common.Volumes + (*VolumesWithBalance)(nil), // 27: common.VolumesWithBalance + (*VolumesByAssets)(nil), // 28: common.VolumesByAssets + (*PostCommitVolumes)(nil), // 29: common.PostCommitVolumes + (*Account)(nil), // 30: common.Account + (*TargetAccount)(nil), // 31: common.TargetAccount + (*Target)(nil), // 32: common.Target + (*MetadataFieldSchema)(nil), // 33: common.MetadataFieldSchema + (*MetadataSchema)(nil), // 34: common.MetadataSchema + (*SetMetadataFieldTypeCommand)(nil), // 35: common.SetMetadataFieldTypeCommand + (*MetadataIndexID)(nil), // 36: common.MetadataIndexID + (*IndexID)(nil), // 37: common.IndexID + (*Index)(nil), // 38: common.Index + (*Idempotency)(nil), // 39: common.Idempotency + (*IdempotencyEntry)(nil), // 40: common.IdempotencyEntry + (*Log)(nil), // 41: common.Log + (*LogPayload)(nil), // 42: common.LogPayload + (*PromotedLedgerLog)(nil), // 43: common.PromotedLedgerLog + (*RegisteredSigningKeyLog)(nil), // 44: common.RegisteredSigningKeyLog + (*RevokedSigningKeyLog)(nil), // 45: common.RevokedSigningKeyLog + (*SigningKey)(nil), // 46: common.SigningKey + (*SetSigningConfigLog)(nil), // 47: common.SetSigningConfigLog + (*AddedEventsSinkLog)(nil), // 48: common.AddedEventsSinkLog + (*RemovedEventsSinkLog)(nil), // 49: common.RemovedEventsSinkLog + (*SetMaintenanceModeLog)(nil), // 50: common.SetMaintenanceModeLog + (*BloomTypeConfig)(nil), // 51: common.BloomTypeConfig + (*ClusterConfig)(nil), // 52: common.ClusterConfig + (*PersistedClusterState)(nil), // 53: common.PersistedClusterState + (*SetChapterScheduleLog)(nil), // 54: common.SetChapterScheduleLog + (*DeletedChapterScheduleLog)(nil), // 55: common.DeletedChapterScheduleLog + (*CreatedPreparedQueryLog)(nil), // 56: common.CreatedPreparedQueryLog + (*UpdatedPreparedQueryLog)(nil), // 57: common.UpdatedPreparedQueryLog + (*DeletedPreparedQueryLog)(nil), // 58: common.DeletedPreparedQueryLog + (*SavedLedgerMetadataLog)(nil), // 59: common.SavedLedgerMetadataLog + (*DeletedLedgerMetadataLog)(nil), // 60: common.DeletedLedgerMetadataLog + (*NumscriptInfo)(nil), // 61: common.NumscriptInfo + (*SavedNumscriptLog)(nil), // 62: common.SavedNumscriptLog + (*DeletedNumscriptLog)(nil), // 63: common.DeletedNumscriptLog + (*TemplateUsage)(nil), // 64: common.TemplateUsage + (*SetQueryCheckpointScheduleLog)(nil), // 65: common.SetQueryCheckpointScheduleLog + (*DeletedQueryCheckpointScheduleLog)(nil), // 66: common.DeletedQueryCheckpointScheduleLog + (*CreatedQueryCheckpointLog)(nil), // 67: common.CreatedQueryCheckpointLog + (*DeletedQueryCheckpointLog)(nil), // 68: common.DeletedQueryCheckpointLog + (*SinkConfig)(nil), // 69: common.SinkConfig + (*SinkStatus)(nil), // 70: common.SinkStatus + (*SinkError)(nil), // 71: common.SinkError + (*NatsSinkConfig)(nil), // 72: common.NatsSinkConfig + (*ClickHouseSinkConfig)(nil), // 73: common.ClickHouseSinkConfig + (*KafkaSinkConfig)(nil), // 74: common.KafkaSinkConfig + (*HttpSinkConfig)(nil), // 75: common.HttpSinkConfig + (*DatabricksSinkConfig)(nil), // 76: common.DatabricksSinkConfig + (*DatabricksOAuthM2M)(nil), // 77: common.DatabricksOAuthM2M + (*CreatedLedgerLog)(nil), // 78: common.CreatedLedgerLog + (*DeletedLedgerLog)(nil), // 79: common.DeletedLedgerLog + (*ApplyLedgerLog)(nil), // 80: common.ApplyLedgerLog + (*LedgerLog)(nil), // 81: common.LedgerLog + (*TouchedVolume)(nil), // 82: common.TouchedVolume + (*LedgerLogPayload)(nil), // 83: common.LedgerLogPayload + (*CreatedIndexLog)(nil), // 84: common.CreatedIndexLog + (*DroppedIndexLog)(nil), // 85: common.DroppedIndexLog + (*FilledGapLog)(nil), // 86: common.FilledGapLog + (*CreatedTransaction)(nil), // 87: common.CreatedTransaction + (*RevertedTransaction)(nil), // 88: common.RevertedTransaction + (*SavedMetadata)(nil), // 89: common.SavedMetadata + (*DeletedMetadata)(nil), // 90: common.DeletedMetadata + (*SetMetadataFieldTypeLog)(nil), // 91: common.SetMetadataFieldTypeLog + (*RemovedMetadataFieldTypeLog)(nil), // 92: common.RemovedMetadataFieldTypeLog + (*Chapter)(nil), // 93: common.Chapter + (*ClosedChapterLog)(nil), // 94: common.ClosedChapterLog + (*SealedChapterLog)(nil), // 95: common.SealedChapterLog + (*ArchivedChapterLog)(nil), // 96: common.ArchivedChapterLog + (*ConfirmedArchiveChapterLog)(nil), // 97: common.ConfirmedArchiveChapterLog + (*MirrorSourceConfig)(nil), // 98: common.MirrorSourceConfig + (*MirrorRewriteRule)(nil), // 99: common.MirrorRewriteRule + (*CreatedTransactionRule)(nil), // 100: common.CreatedTransactionRule + (*RevertedTransactionRule)(nil), // 101: common.RevertedTransactionRule + (*SavedMetadataRule)(nil), // 102: common.SavedMetadataRule + (*DeletedMetadataRule)(nil), // 103: common.DeletedMetadataRule + (*AnyVariantRule)(nil), // 104: common.AnyVariantRule + (*CreatedTransactionAction)(nil), // 105: common.CreatedTransactionAction + (*RevertedTransactionAction)(nil), // 106: common.RevertedTransactionAction + (*SavedMetadataAction)(nil), // 107: common.SavedMetadataAction + (*DeletedMetadataAction)(nil), // 108: common.DeletedMetadataAction + (*AnyVariantAction)(nil), // 109: common.AnyVariantAction + (*RewriteAddressAction)(nil), // 110: common.RewriteAddressAction + (*SetMetadataAction)(nil), // 111: common.SetMetadataAction + (*DeleteMetadataAction)(nil), // 112: common.DeleteMetadataAction + (*SetAccountMetadataAction)(nil), // 113: common.SetAccountMetadataAction + (*DeleteAccountMetadataAction)(nil), // 114: common.DeleteAccountMetadataAction + (*SetAccountMetadataFromAddressAction)(nil), // 115: common.SetAccountMetadataFromAddressAction + (*SetAccountMetadataFromAddressReplacement)(nil), // 116: common.SetAccountMetadataFromAddressReplacement + (*DropAction)(nil), // 117: common.DropAction + (*HttpMirrorSourceConfig)(nil), // 118: common.HttpMirrorSourceConfig + (*OAuth2ClientCredentials)(nil), // 119: common.OAuth2ClientCredentials + (*PostgresMirrorSourceConfig)(nil), // 120: common.PostgresMirrorSourceConfig + (*PostgresAwsIamAuth)(nil), // 121: common.PostgresAwsIamAuth + (*MirrorSyncError)(nil), // 122: common.MirrorSyncError + (*MirrorSyncProgress)(nil), // 123: common.MirrorSyncProgress + (*LedgerInfo)(nil), // 124: common.LedgerInfo + (*SaveMetadataCommand)(nil), // 125: common.SaveMetadataCommand + (*DeleteMetadataCommand)(nil), // 126: common.DeleteMetadataCommand + (*TransactionState)(nil), // 127: common.TransactionState + (*IdempotencyKeyValue)(nil), // 128: common.IdempotencyKeyValue + (*IdempotencyFailure)(nil), // 129: common.IdempotencyFailure + (*TransactionReferenceValue)(nil), // 130: common.TransactionReferenceValue + (*NumscriptVersionValue)(nil), // 131: common.NumscriptVersionValue + (*SegmentType)(nil), // 132: common.SegmentType + (*UUIDConstraint)(nil), // 133: common.UUIDConstraint + (*Uint64Constraint)(nil), // 134: common.Uint64Constraint + (*BytesConstraint)(nil), // 135: common.BytesConstraint + (*AccountType)(nil), // 136: common.AccountType + (*AddedAccountTypeLog)(nil), // 137: common.AddedAccountTypeLog + (*RemovedAccountTypeLog)(nil), // 138: common.RemovedAccountTypeLog + (*UpdatedDefaultEnforcementModeLog)(nil), // 139: common.UpdatedDefaultEnforcementModeLog + (*QueryFilter)(nil), // 140: common.QueryFilter + (*ReferenceCondition)(nil), // 141: common.ReferenceCondition + (*RevertedCondition)(nil), // 142: common.RevertedCondition + (*LedgerCondition)(nil), // 143: common.LedgerCondition + (*LogIdCondition)(nil), // 144: common.LogIdCondition + (*BuiltinUintCondition)(nil), // 145: common.BuiltinUintCondition + (*LogBuiltinUintCondition)(nil), // 146: common.LogBuiltinUintCondition + (*AccountHasAssetCondition)(nil), // 147: common.AccountHasAssetCondition + (*AndFilter)(nil), // 148: common.AndFilter + (*OrFilter)(nil), // 149: common.OrFilter + (*NotFilter)(nil), // 150: common.NotFilter + (*FieldRef)(nil), // 151: common.FieldRef + (*FieldCondition)(nil), // 152: common.FieldCondition + (*StringCondition)(nil), // 153: common.StringCondition + (*IntCondition)(nil), // 154: common.IntCondition + (*UintCondition)(nil), // 155: common.UintCondition + (*BoolCondition)(nil), // 156: common.BoolCondition + (*ExistsCondition)(nil), // 157: common.ExistsCondition + (*AddressMatch)(nil), // 158: common.AddressMatch + (*PreparedQuery)(nil), // 159: common.PreparedQuery + (*AggregatedVolume)(nil), // 160: common.AggregatedVolume + (*AggregateResult)(nil), // 161: common.AggregateResult + (*GroupedAggregateResult)(nil), // 162: common.GroupedAggregateResult + (*PreparedQueryCursor)(nil), // 163: common.PreparedQueryCursor + (*LedgerStats)(nil), // 164: common.LedgerStats + (*PersistedConfig)(nil), // 165: common.PersistedConfig + (*CallerIdentity)(nil), // 166: common.CallerIdentity + (*CallerSnapshot)(nil), // 167: common.CallerSnapshot + (*S3StorageConfig)(nil), // 168: common.S3StorageConfig + (*AzureStorageConfig)(nil), // 169: common.AzureStorageConfig + (*BackupStorage)(nil), // 170: common.BackupStorage + (*ReadOptions)(nil), // 171: common.ReadOptions + (*ListOptions)(nil), // 172: common.ListOptions + nil, // 173: common.MetadataMap.ValuesEntry + nil, // 174: common.Transaction.MetadataEntry + nil, // 175: common.Script.VarsEntry + nil, // 176: common.VolumesByAssets.VolumesEntry + nil, // 177: common.PostCommitVolumes.VolumesByAccountEntry + nil, // 178: common.Account.MetadataEntry + nil, // 179: common.Account.VolumesEntry + nil, // 180: common.MetadataSchema.AccountFieldsEntry + nil, // 181: common.MetadataSchema.TransactionFieldsEntry + nil, // 182: common.MetadataSchema.LedgerFieldsEntry + nil, // 183: common.SavedLedgerMetadataLog.MetadataEntry + nil, // 184: common.CreatedLedgerLog.AccountTypesEntry + nil, // 185: common.CreatedTransaction.AccountMetadataEntry + nil, // 186: common.SavedMetadata.MetadataEntry + nil, // 187: common.LedgerInfo.AccountTypesEntry + nil, // 188: common.LedgerInfo.MetadataEntry + nil, // 189: common.SaveMetadataCommand.MetadataEntry + nil, // 190: common.TransactionState.MetadataEntry + nil, // 191: common.IdempotencyFailure.MetadataEntry + nil, // 192: common.AccountType.SegmentTypesEntry + (*signaturepb.SignedLog)(nil), // 193: signature.SignedLog } var file_common_proto_depIdxs = []int32{ - 19, // 0: common.MetadataValue.null_value:type_name -> common.NullValue - 175, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry - 23, // 2: common.Posting.amount:type_name -> common.Uint256 - 24, // 3: common.Transaction.postings:type_name -> common.Posting - 176, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry - 18, // 5: common.Transaction.timestamp:type_name -> common.Timestamp - 18, // 6: common.Transaction.inserted_at:type_name -> common.Timestamp - 18, // 7: common.Transaction.updated_at:type_name -> common.Timestamp - 18, // 8: common.Transaction.reverted_at:type_name -> common.Timestamp - 177, // 9: common.Script.vars:type_name -> common.Script.VarsEntry - 178, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry - 179, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry - 180, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry - 18, // 13: common.Account.first_usage:type_name -> common.Timestamp - 18, // 14: common.Account.insertion_date:type_name -> common.Timestamp - 18, // 15: common.Account.updated_at:type_name -> common.Timestamp - 181, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry - 32, // 17: common.Target.account:type_name -> common.TargetAccount + 18, // 0: common.MetadataValue.null_value:type_name -> common.NullValue + 173, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry + 22, // 2: common.Posting.amount:type_name -> common.Uint256 + 23, // 3: common.Transaction.postings:type_name -> common.Posting + 174, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry + 17, // 5: common.Transaction.timestamp:type_name -> common.Timestamp + 17, // 6: common.Transaction.inserted_at:type_name -> common.Timestamp + 17, // 7: common.Transaction.updated_at:type_name -> common.Timestamp + 17, // 8: common.Transaction.reverted_at:type_name -> common.Timestamp + 175, // 9: common.Script.vars:type_name -> common.Script.VarsEntry + 176, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry + 177, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry + 178, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry + 17, // 13: common.Account.first_usage:type_name -> common.Timestamp + 17, // 14: common.Account.insertion_date:type_name -> common.Timestamp + 17, // 15: common.Account.updated_at:type_name -> common.Timestamp + 179, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry + 31, // 17: common.Target.account:type_name -> common.TargetAccount 1, // 18: common.MetadataFieldSchema.type:type_name -> common.MetadataType - 182, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry - 183, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry - 184, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry + 180, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry + 181, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry + 182, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry 0, // 22: common.SetMetadataFieldTypeCommand.target_type:type_name -> common.TargetType 1, // 23: common.SetMetadataFieldTypeCommand.type:type_name -> common.MetadataType 0, // 24: common.MetadataIndexID.target:type_name -> common.TargetType 3, // 25: common.IndexID.tx_builtin:type_name -> common.TransactionBuiltinIndex 5, // 26: common.IndexID.log_builtin:type_name -> common.LogBuiltinIndex 4, // 27: common.IndexID.account_builtin:type_name -> common.AccountBuiltinIndex - 37, // 28: common.IndexID.metadata:type_name -> common.MetadataIndexID - 38, // 29: common.Index.id:type_name -> common.IndexID + 36, // 28: common.IndexID.metadata:type_name -> common.MetadataIndexID + 37, // 29: common.Index.id:type_name -> common.IndexID 2, // 30: common.Index.build_status:type_name -> common.IndexBuildStatus - 18, // 31: common.Index.created_at:type_name -> common.Timestamp - 18, // 32: common.Index.last_built_at:type_name -> common.Timestamp - 43, // 33: common.Log.payload:type_name -> common.LogPayload - 195, // 34: common.Log.response_signature:type_name -> signature.SignedLog - 79, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog - 80, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog - 81, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog - 45, // 38: common.LogPayload.register_signing_key:type_name -> common.RegisteredSigningKeyLog - 46, // 39: common.LogPayload.revoke_signing_key:type_name -> common.RevokedSigningKeyLog - 48, // 40: common.LogPayload.set_signing_config:type_name -> common.SetSigningConfigLog - 49, // 41: common.LogPayload.added_events_sink:type_name -> common.AddedEventsSinkLog - 50, // 42: common.LogPayload.removed_events_sink:type_name -> common.RemovedEventsSinkLog - 95, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog - 96, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog - 97, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog - 98, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog - 51, // 47: common.LogPayload.set_maintenance_mode:type_name -> common.SetMaintenanceModeLog - 55, // 48: common.LogPayload.set_chapter_schedule:type_name -> common.SetChapterScheduleLog - 56, // 49: common.LogPayload.delete_chapter_schedule:type_name -> common.DeletedChapterScheduleLog - 44, // 50: common.LogPayload.promote_ledger:type_name -> common.PromotedLedgerLog - 57, // 51: common.LogPayload.created_prepared_query:type_name -> common.CreatedPreparedQueryLog - 58, // 52: common.LogPayload.updated_prepared_query:type_name -> common.UpdatedPreparedQueryLog - 59, // 53: common.LogPayload.deleted_prepared_query:type_name -> common.DeletedPreparedQueryLog - 63, // 54: common.LogPayload.saved_numscript:type_name -> common.SavedNumscriptLog - 64, // 55: common.LogPayload.deleted_numscript:type_name -> common.DeletedNumscriptLog - 68, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog - 69, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog - 66, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog - 67, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog - 60, // 60: common.LogPayload.saved_ledger_metadata:type_name -> common.SavedLedgerMetadataLog - 61, // 61: common.LogPayload.deleted_ledger_metadata:type_name -> common.DeletedLedgerMetadataLog - 70, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig - 52, // 63: common.ClusterConfig.bloom_volumes:type_name -> common.BloomTypeConfig - 52, // 64: common.ClusterConfig.bloom_metadata:type_name -> common.BloomTypeConfig - 52, // 65: common.ClusterConfig.bloom_references:type_name -> common.BloomTypeConfig - 52, // 66: common.ClusterConfig.bloom_ledgers:type_name -> common.BloomTypeConfig - 52, // 67: common.ClusterConfig.bloom_boundaries:type_name -> common.BloomTypeConfig - 52, // 68: common.ClusterConfig.bloom_transactions:type_name -> common.BloomTypeConfig - 52, // 69: common.ClusterConfig.bloom_sink_configs:type_name -> common.BloomTypeConfig - 52, // 70: common.ClusterConfig.bloom_numscript_versions:type_name -> common.BloomTypeConfig - 52, // 71: common.ClusterConfig.bloom_numscript_contents:type_name -> common.BloomTypeConfig + 17, // 31: common.Index.created_at:type_name -> common.Timestamp + 17, // 32: common.Index.last_built_at:type_name -> common.Timestamp + 42, // 33: common.Log.payload:type_name -> common.LogPayload + 193, // 34: common.Log.response_signature:type_name -> signature.SignedLog + 78, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog + 79, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog + 80, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog + 44, // 38: common.LogPayload.register_signing_key:type_name -> common.RegisteredSigningKeyLog + 45, // 39: common.LogPayload.revoke_signing_key:type_name -> common.RevokedSigningKeyLog + 47, // 40: common.LogPayload.set_signing_config:type_name -> common.SetSigningConfigLog + 48, // 41: common.LogPayload.added_events_sink:type_name -> common.AddedEventsSinkLog + 49, // 42: common.LogPayload.removed_events_sink:type_name -> common.RemovedEventsSinkLog + 94, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog + 95, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog + 96, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog + 97, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog + 50, // 47: common.LogPayload.set_maintenance_mode:type_name -> common.SetMaintenanceModeLog + 54, // 48: common.LogPayload.set_chapter_schedule:type_name -> common.SetChapterScheduleLog + 55, // 49: common.LogPayload.delete_chapter_schedule:type_name -> common.DeletedChapterScheduleLog + 43, // 50: common.LogPayload.promote_ledger:type_name -> common.PromotedLedgerLog + 56, // 51: common.LogPayload.created_prepared_query:type_name -> common.CreatedPreparedQueryLog + 57, // 52: common.LogPayload.updated_prepared_query:type_name -> common.UpdatedPreparedQueryLog + 58, // 53: common.LogPayload.deleted_prepared_query:type_name -> common.DeletedPreparedQueryLog + 62, // 54: common.LogPayload.saved_numscript:type_name -> common.SavedNumscriptLog + 63, // 55: common.LogPayload.deleted_numscript:type_name -> common.DeletedNumscriptLog + 67, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog + 68, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog + 65, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog + 66, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog + 59, // 60: common.LogPayload.saved_ledger_metadata:type_name -> common.SavedLedgerMetadataLog + 60, // 61: common.LogPayload.deleted_ledger_metadata:type_name -> common.DeletedLedgerMetadataLog + 69, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig + 51, // 63: common.ClusterConfig.bloom_volumes:type_name -> common.BloomTypeConfig + 51, // 64: common.ClusterConfig.bloom_metadata:type_name -> common.BloomTypeConfig + 51, // 65: common.ClusterConfig.bloom_references:type_name -> common.BloomTypeConfig + 51, // 66: common.ClusterConfig.bloom_ledgers:type_name -> common.BloomTypeConfig + 51, // 67: common.ClusterConfig.bloom_boundaries:type_name -> common.BloomTypeConfig + 51, // 68: common.ClusterConfig.bloom_transactions:type_name -> common.BloomTypeConfig + 51, // 69: common.ClusterConfig.bloom_sink_configs:type_name -> common.BloomTypeConfig + 51, // 70: common.ClusterConfig.bloom_numscript_versions:type_name -> common.BloomTypeConfig + 51, // 71: common.ClusterConfig.bloom_numscript_contents:type_name -> common.BloomTypeConfig 6, // 72: common.ClusterConfig.hash_algorithm:type_name -> common.HashAlgorithm - 52, // 73: common.ClusterConfig.bloom_ledger_metadata:type_name -> common.BloomTypeConfig - 52, // 74: common.ClusterConfig.bloom_prepared_queries:type_name -> common.BloomTypeConfig - 52, // 75: common.ClusterConfig.bloom_indexes:type_name -> common.BloomTypeConfig - 53, // 76: common.PersistedClusterState.config:type_name -> common.ClusterConfig - 161, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery - 141, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter - 141, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter - 185, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry - 18, // 81: common.NumscriptInfo.created_at:type_name -> common.Timestamp - 62, // 82: common.SavedNumscriptLog.info:type_name -> common.NumscriptInfo - 18, // 83: common.TemplateUsage.last_used:type_name -> common.Timestamp - 73, // 84: common.SinkConfig.nats:type_name -> common.NatsSinkConfig - 74, // 85: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig - 75, // 86: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig - 76, // 87: common.SinkConfig.http:type_name -> common.HttpSinkConfig - 77, // 88: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig + 51, // 73: common.ClusterConfig.bloom_ledger_metadata:type_name -> common.BloomTypeConfig + 51, // 74: common.ClusterConfig.bloom_prepared_queries:type_name -> common.BloomTypeConfig + 51, // 75: common.ClusterConfig.bloom_indexes:type_name -> common.BloomTypeConfig + 52, // 76: common.PersistedClusterState.config:type_name -> common.ClusterConfig + 159, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery + 140, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter + 140, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter + 183, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry + 17, // 81: common.NumscriptInfo.created_at:type_name -> common.Timestamp + 61, // 82: common.SavedNumscriptLog.info:type_name -> common.NumscriptInfo + 17, // 83: common.TemplateUsage.last_used:type_name -> common.Timestamp + 72, // 84: common.SinkConfig.nats:type_name -> common.NatsSinkConfig + 73, // 85: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig + 74, // 86: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig + 75, // 87: common.SinkConfig.http:type_name -> common.HttpSinkConfig + 76, // 88: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig 7, // 89: common.SinkConfig.event_types:type_name -> common.EventType - 72, // 90: common.SinkStatus.error:type_name -> common.SinkError - 18, // 91: common.SinkError.occurred_at:type_name -> common.Timestamp - 78, // 92: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M - 18, // 93: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp - 35, // 94: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema + 71, // 90: common.SinkStatus.error:type_name -> common.SinkError + 17, // 91: common.SinkError.occurred_at:type_name -> common.Timestamp + 77, // 92: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M + 17, // 93: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp + 34, // 94: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema 9, // 95: common.CreatedLedgerLog.mode:type_name -> common.LedgerMode - 99, // 96: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig - 186, // 97: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry + 98, // 96: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig + 184, // 97: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry 12, // 98: common.CreatedLedgerLog.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 18, // 99: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp - 82, // 100: common.ApplyLedgerLog.log:type_name -> common.LedgerLog - 84, // 101: common.LedgerLog.data:type_name -> common.LedgerLogPayload - 18, // 102: common.LedgerLog.date:type_name -> common.Timestamp - 83, // 103: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume - 88, // 104: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction - 89, // 105: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction - 90, // 106: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata - 91, // 107: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata - 92, // 108: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog - 93, // 109: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog - 87, // 110: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog - 85, // 111: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog - 86, // 112: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog - 138, // 113: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog - 139, // 114: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog - 140, // 115: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog - 38, // 116: common.CreatedIndexLog.id:type_name -> common.IndexID - 38, // 117: common.DroppedIndexLog.id:type_name -> common.IndexID - 25, // 118: common.CreatedTransaction.transaction:type_name -> common.Transaction - 187, // 119: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry - 30, // 120: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 25, // 121: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction - 30, // 122: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 33, // 123: common.SavedMetadata.target:type_name -> common.Target - 188, // 124: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry - 33, // 125: common.DeletedMetadata.target:type_name -> common.Target - 0, // 126: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 1, // 127: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType - 0, // 128: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 38, // 129: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID - 18, // 130: common.Chapter.start:type_name -> common.Timestamp - 18, // 131: common.Chapter.end:type_name -> common.Timestamp - 8, // 132: common.Chapter.status:type_name -> common.ChapterStatus - 94, // 133: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter - 94, // 134: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter - 94, // 135: common.SealedChapterLog.chapter:type_name -> common.Chapter - 94, // 136: common.ArchivedChapterLog.chapter:type_name -> common.Chapter - 94, // 137: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter - 119, // 138: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig - 121, // 139: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig - 100, // 140: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule - 101, // 141: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule - 102, // 142: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule - 103, // 143: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule - 104, // 144: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule - 105, // 145: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule - 106, // 146: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction - 107, // 147: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction - 108, // 148: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction - 109, // 149: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction - 110, // 150: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction - 111, // 151: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 112, // 152: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 113, // 153: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 114, // 154: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction - 115, // 155: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction - 116, // 156: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction - 118, // 157: common.CreatedTransactionAction.drop:type_name -> common.DropAction - 111, // 158: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 112, // 159: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 113, // 160: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 118, // 161: common.RevertedTransactionAction.drop:type_name -> common.DropAction - 111, // 162: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 112, // 163: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction - 113, // 164: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction - 118, // 165: common.SavedMetadataAction.drop:type_name -> common.DropAction - 111, // 166: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 118, // 167: common.DeletedMetadataAction.drop:type_name -> common.DropAction - 111, // 168: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction - 118, // 169: common.AnyVariantAction.drop:type_name -> common.DropAction - 117, // 170: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement - 120, // 171: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials - 122, // 172: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth - 18, // 173: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp - 10, // 174: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState - 123, // 175: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError - 18, // 176: common.LedgerInfo.created_at:type_name -> common.Timestamp - 18, // 177: common.LedgerInfo.deleted_at:type_name -> common.Timestamp - 35, // 178: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema - 9, // 179: common.LedgerInfo.mode:type_name -> common.LedgerMode - 99, // 180: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig - 124, // 181: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress - 189, // 182: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry - 12, // 183: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 190, // 184: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry - 33, // 185: common.SaveMetadataCommand.target:type_name -> common.Target - 191, // 186: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry - 33, // 187: common.DeleteMetadataCommand.target:type_name -> common.Target - 192, // 188: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry - 18, // 189: common.TransactionState.timestamp:type_name -> common.Timestamp - 24, // 190: common.TransactionState.postings:type_name -> common.Posting - 18, // 191: common.TransactionState.reverted_at:type_name -> common.Timestamp - 130, // 192: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure - 11, // 193: common.IdempotencyFailure.reason:type_name -> common.ErrorReason - 193, // 194: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry - 134, // 195: common.SegmentType.uuid:type_name -> common.UUIDConstraint - 135, // 196: common.SegmentType.uint64:type_name -> common.Uint64Constraint - 136, // 197: common.SegmentType.bytes:type_name -> common.BytesConstraint - 13, // 198: common.AccountType.persistence:type_name -> common.AccountTypePersistence - 194, // 199: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry - 137, // 200: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType - 12, // 201: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode - 154, // 202: common.QueryFilter.field:type_name -> common.FieldCondition - 160, // 203: common.QueryFilter.address:type_name -> common.AddressMatch - 150, // 204: common.QueryFilter.and:type_name -> common.AndFilter - 151, // 205: common.QueryFilter.or:type_name -> common.OrFilter - 152, // 206: common.QueryFilter.not:type_name -> common.NotFilter - 142, // 207: common.QueryFilter.reference:type_name -> common.ReferenceCondition - 147, // 208: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition - 145, // 209: common.QueryFilter.ledger:type_name -> common.LedgerCondition - 146, // 210: common.QueryFilter.log_id:type_name -> common.LogIdCondition - 148, // 211: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition - 149, // 212: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition - 143, // 213: common.QueryFilter.reverted:type_name -> common.RevertedCondition - 144, // 214: common.QueryFilter.audit:type_name -> common.AuditCondition - 155, // 215: common.ReferenceCondition.cond:type_name -> common.StringCondition - 14, // 216: common.AuditCondition.field:type_name -> common.AuditField - 155, // 217: common.AuditCondition.string_cond:type_name -> common.StringCondition - 157, // 218: common.AuditCondition.uint_cond:type_name -> common.UintCondition - 155, // 219: common.LedgerCondition.cond:type_name -> common.StringCondition - 157, // 220: common.LogIdCondition.cond:type_name -> common.UintCondition - 3, // 221: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex - 157, // 222: common.BuiltinUintCondition.cond:type_name -> common.UintCondition - 5, // 223: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex - 157, // 224: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition - 141, // 225: common.AndFilter.filters:type_name -> common.QueryFilter - 141, // 226: common.OrFilter.filters:type_name -> common.QueryFilter - 141, // 227: common.NotFilter.filter:type_name -> common.QueryFilter - 153, // 228: common.FieldCondition.field:type_name -> common.FieldRef - 155, // 229: common.FieldCondition.string_cond:type_name -> common.StringCondition - 156, // 230: common.FieldCondition.int_cond:type_name -> common.IntCondition - 157, // 231: common.FieldCondition.uint_cond:type_name -> common.UintCondition - 158, // 232: common.FieldCondition.bool_cond:type_name -> common.BoolCondition - 159, // 233: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition - 15, // 234: common.AddressMatch.role:type_name -> common.AddressRole - 141, // 235: common.PreparedQuery.filter:type_name -> common.QueryFilter - 16, // 236: common.PreparedQuery.target:type_name -> common.QueryTarget - 23, // 237: common.AggregatedVolume.input:type_name -> common.Uint256 - 23, // 238: common.AggregatedVolume.output:type_name -> common.Uint256 - 162, // 239: common.AggregateResult.volumes:type_name -> common.AggregatedVolume - 164, // 240: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult - 162, // 241: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume - 31, // 242: common.PreparedQueryCursor.account_data:type_name -> common.Account - 25, // 243: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction - 168, // 244: common.CallerSnapshot.identity:type_name -> common.CallerIdentity - 170, // 245: common.BackupStorage.s3:type_name -> common.S3StorageConfig - 171, // 246: common.BackupStorage.azure:type_name -> common.AzureStorageConfig - 173, // 247: common.ListOptions.read:type_name -> common.ReadOptions - 141, // 248: common.ListOptions.filter:type_name -> common.QueryFilter - 20, // 249: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue - 20, // 250: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue - 27, // 251: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes - 29, // 252: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets - 20, // 253: common.Account.MetadataEntry.value:type_name -> common.MetadataValue - 28, // 254: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance - 34, // 255: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema - 34, // 256: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema - 34, // 257: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema - 20, // 258: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue - 137, // 259: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType - 21, // 260: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap - 20, // 261: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue - 137, // 262: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType - 20, // 263: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue - 20, // 264: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue - 20, // 265: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue - 133, // 266: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType - 267, // [267:267] is the sub-list for method output_type - 267, // [267:267] is the sub-list for method input_type - 267, // [267:267] is the sub-list for extension type_name - 267, // [267:267] is the sub-list for extension extendee - 0, // [0:267] is the sub-list for field type_name + 17, // 99: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp + 81, // 100: common.ApplyLedgerLog.log:type_name -> common.LedgerLog + 83, // 101: common.LedgerLog.data:type_name -> common.LedgerLogPayload + 17, // 102: common.LedgerLog.date:type_name -> common.Timestamp + 82, // 103: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume + 82, // 104: common.LedgerLog.new_volumes:type_name -> common.TouchedVolume + 87, // 105: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction + 88, // 106: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction + 89, // 107: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata + 90, // 108: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata + 91, // 109: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog + 92, // 110: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog + 86, // 111: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog + 84, // 112: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog + 85, // 113: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog + 137, // 114: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog + 138, // 115: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog + 139, // 116: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog + 37, // 117: common.CreatedIndexLog.id:type_name -> common.IndexID + 37, // 118: common.DroppedIndexLog.id:type_name -> common.IndexID + 24, // 119: common.CreatedTransaction.transaction:type_name -> common.Transaction + 185, // 120: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry + 29, // 121: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 24, // 122: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction + 29, // 123: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 32, // 124: common.SavedMetadata.target:type_name -> common.Target + 186, // 125: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry + 32, // 126: common.DeletedMetadata.target:type_name -> common.Target + 0, // 127: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 1, // 128: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType + 0, // 129: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 37, // 130: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID + 17, // 131: common.Chapter.start:type_name -> common.Timestamp + 17, // 132: common.Chapter.end:type_name -> common.Timestamp + 8, // 133: common.Chapter.status:type_name -> common.ChapterStatus + 93, // 134: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter + 93, // 135: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter + 93, // 136: common.SealedChapterLog.chapter:type_name -> common.Chapter + 93, // 137: common.ArchivedChapterLog.chapter:type_name -> common.Chapter + 93, // 138: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter + 118, // 139: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig + 120, // 140: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig + 99, // 141: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule + 100, // 142: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule + 101, // 143: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule + 102, // 144: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule + 103, // 145: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule + 104, // 146: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule + 105, // 147: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction + 106, // 148: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction + 107, // 149: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction + 108, // 150: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction + 109, // 151: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction + 110, // 152: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 111, // 153: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 112, // 154: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 113, // 155: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction + 114, // 156: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction + 115, // 157: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction + 117, // 158: common.CreatedTransactionAction.drop:type_name -> common.DropAction + 110, // 159: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 111, // 160: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 112, // 161: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 117, // 162: common.RevertedTransactionAction.drop:type_name -> common.DropAction + 110, // 163: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 111, // 164: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction + 112, // 165: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction + 117, // 166: common.SavedMetadataAction.drop:type_name -> common.DropAction + 110, // 167: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 117, // 168: common.DeletedMetadataAction.drop:type_name -> common.DropAction + 110, // 169: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction + 117, // 170: common.AnyVariantAction.drop:type_name -> common.DropAction + 116, // 171: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement + 119, // 172: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials + 121, // 173: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth + 17, // 174: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp + 10, // 175: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState + 122, // 176: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError + 17, // 177: common.LedgerInfo.created_at:type_name -> common.Timestamp + 17, // 178: common.LedgerInfo.deleted_at:type_name -> common.Timestamp + 34, // 179: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema + 9, // 180: common.LedgerInfo.mode:type_name -> common.LedgerMode + 98, // 181: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig + 123, // 182: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress + 187, // 183: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry + 12, // 184: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode + 188, // 185: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry + 32, // 186: common.SaveMetadataCommand.target:type_name -> common.Target + 189, // 187: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry + 32, // 188: common.DeleteMetadataCommand.target:type_name -> common.Target + 190, // 189: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry + 17, // 190: common.TransactionState.timestamp:type_name -> common.Timestamp + 23, // 191: common.TransactionState.postings:type_name -> common.Posting + 17, // 192: common.TransactionState.reverted_at:type_name -> common.Timestamp + 129, // 193: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure + 11, // 194: common.IdempotencyFailure.reason:type_name -> common.ErrorReason + 191, // 195: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry + 133, // 196: common.SegmentType.uuid:type_name -> common.UUIDConstraint + 134, // 197: common.SegmentType.uint64:type_name -> common.Uint64Constraint + 135, // 198: common.SegmentType.bytes:type_name -> common.BytesConstraint + 13, // 199: common.AccountType.persistence:type_name -> common.AccountTypePersistence + 192, // 200: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry + 136, // 201: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType + 12, // 202: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode + 152, // 203: common.QueryFilter.field:type_name -> common.FieldCondition + 158, // 204: common.QueryFilter.address:type_name -> common.AddressMatch + 148, // 205: common.QueryFilter.and:type_name -> common.AndFilter + 149, // 206: common.QueryFilter.or:type_name -> common.OrFilter + 150, // 207: common.QueryFilter.not:type_name -> common.NotFilter + 141, // 208: common.QueryFilter.reference:type_name -> common.ReferenceCondition + 145, // 209: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition + 143, // 210: common.QueryFilter.ledger:type_name -> common.LedgerCondition + 144, // 211: common.QueryFilter.log_id:type_name -> common.LogIdCondition + 146, // 212: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition + 147, // 213: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition + 142, // 214: common.QueryFilter.reverted:type_name -> common.RevertedCondition + 153, // 215: common.ReferenceCondition.cond:type_name -> common.StringCondition + 153, // 216: common.LedgerCondition.cond:type_name -> common.StringCondition + 155, // 217: common.LogIdCondition.cond:type_name -> common.UintCondition + 3, // 218: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex + 155, // 219: common.BuiltinUintCondition.cond:type_name -> common.UintCondition + 5, // 220: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex + 155, // 221: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition + 140, // 222: common.AndFilter.filters:type_name -> common.QueryFilter + 140, // 223: common.OrFilter.filters:type_name -> common.QueryFilter + 140, // 224: common.NotFilter.filter:type_name -> common.QueryFilter + 151, // 225: common.FieldCondition.field:type_name -> common.FieldRef + 153, // 226: common.FieldCondition.string_cond:type_name -> common.StringCondition + 154, // 227: common.FieldCondition.int_cond:type_name -> common.IntCondition + 155, // 228: common.FieldCondition.uint_cond:type_name -> common.UintCondition + 156, // 229: common.FieldCondition.bool_cond:type_name -> common.BoolCondition + 157, // 230: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition + 14, // 231: common.AddressMatch.role:type_name -> common.AddressRole + 140, // 232: common.PreparedQuery.filter:type_name -> common.QueryFilter + 15, // 233: common.PreparedQuery.target:type_name -> common.QueryTarget + 22, // 234: common.AggregatedVolume.input:type_name -> common.Uint256 + 22, // 235: common.AggregatedVolume.output:type_name -> common.Uint256 + 160, // 236: common.AggregateResult.volumes:type_name -> common.AggregatedVolume + 162, // 237: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult + 160, // 238: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume + 30, // 239: common.PreparedQueryCursor.account_data:type_name -> common.Account + 24, // 240: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction + 166, // 241: common.CallerSnapshot.identity:type_name -> common.CallerIdentity + 168, // 242: common.BackupStorage.s3:type_name -> common.S3StorageConfig + 169, // 243: common.BackupStorage.azure:type_name -> common.AzureStorageConfig + 171, // 244: common.ListOptions.read:type_name -> common.ReadOptions + 140, // 245: common.ListOptions.filter:type_name -> common.QueryFilter + 19, // 246: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue + 19, // 247: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue + 26, // 248: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes + 28, // 249: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets + 19, // 250: common.Account.MetadataEntry.value:type_name -> common.MetadataValue + 27, // 251: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance + 33, // 252: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema + 33, // 253: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema + 33, // 254: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema + 19, // 255: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue + 136, // 256: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType + 20, // 257: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap + 19, // 258: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue + 136, // 259: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType + 19, // 260: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue + 19, // 261: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue + 19, // 262: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue + 132, // 263: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType + 264, // [264:264] is the sub-list for method output_type + 264, // [264:264] is the sub-list for method input_type + 264, // [264:264] is the sub-list for extension type_name + 264, // [264:264] is the sub-list for extension extendee + 0, // [0:264] is the sub-list for field type_name } func init() { file_common_proto_init() } @@ -14104,41 +13908,36 @@ func file_common_proto_init() { (*QueryFilter_LogBuiltinUint)(nil), (*QueryFilter_AccountHasAsset)(nil), (*QueryFilter_Reverted)(nil), - (*QueryFilter_Audit)(nil), - } - file_common_proto_msgTypes[126].OneofWrappers = []any{ - (*AuditCondition_StringCond)(nil), - (*AuditCondition_UintCond)(nil), } - file_common_proto_msgTypes[136].OneofWrappers = []any{ + file_common_proto_msgTypes[135].OneofWrappers = []any{ (*FieldCondition_StringCond)(nil), (*FieldCondition_IntCond)(nil), (*FieldCondition_UintCond)(nil), (*FieldCondition_BoolCond)(nil), (*FieldCondition_ExistsCond)(nil), } - file_common_proto_msgTypes[137].OneofWrappers = []any{ + file_common_proto_msgTypes[136].OneofWrappers = []any{ (*StringCondition_Hardcoded)(nil), (*StringCondition_Param)(nil), } + file_common_proto_msgTypes[137].OneofWrappers = []any{} file_common_proto_msgTypes[138].OneofWrappers = []any{} - file_common_proto_msgTypes[139].OneofWrappers = []any{} - file_common_proto_msgTypes[140].OneofWrappers = []any{ + file_common_proto_msgTypes[139].OneofWrappers = []any{ (*BoolCondition_Hardcoded)(nil), (*BoolCondition_Param)(nil), } - file_common_proto_msgTypes[142].OneofWrappers = []any{ + file_common_proto_msgTypes[141].OneofWrappers = []any{ (*AddressMatch_HardcodedPrefix)(nil), (*AddressMatch_HardcodedExact)(nil), (*AddressMatch_ParamPrefix)(nil), (*AddressMatch_ParamExact)(nil), } - file_common_proto_msgTypes[150].OneofWrappers = []any{ + file_common_proto_msgTypes[149].OneofWrappers = []any{ (*CallerIdentity_Issuer)(nil), (*CallerIdentity_KeyId)(nil), (*CallerIdentity_SystemComponent)(nil), } - file_common_proto_msgTypes[154].OneofWrappers = []any{ + file_common_proto_msgTypes[153].OneofWrappers = []any{ (*BackupStorage_S3)(nil), (*BackupStorage_Azure)(nil), } @@ -14147,8 +13946,8 @@ func file_common_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), - NumEnums: 18, - NumMessages: 177, + NumEnums: 17, + NumMessages: 176, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/commonpb/common_dethash.pb.go b/internal/proto/commonpb/common_dethash.pb.go index d832cc3ba2..26edd9b2f7 100644 --- a/internal/proto/commonpb/common_dethash.pb.go +++ b/internal/proto/commonpb/common_dethash.pb.go @@ -1660,6 +1660,15 @@ func (m *LedgerLog) MarshalToSizedBufferDeterministicVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.NewVolumes) > 0 { + for iNdEx := len(m.NewVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, _ := m.NewVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + } if len(m.PurgedVolumes) > 0 { for iNdEx := len(m.PurgedVolumes) - 1; iNdEx >= 0; iNdEx-- { size, _ := m.PurgedVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) diff --git a/internal/proto/commonpb/common_reader.pb.go b/internal/proto/commonpb/common_reader.pb.go index ee0744112d..c44b6d0aa2 100644 --- a/internal/proto/commonpb/common_reader.pb.go +++ b/internal/proto/commonpb/common_reader.pb.go @@ -5333,6 +5333,7 @@ type LedgerLogReader interface { GetDate() TimestampReader GetId() uint64 GetPurgedVolumes() TouchedVolumeListReader + GetNewVolumes() TouchedVolumeListReader Mutate() *LedgerLog } @@ -5362,6 +5363,10 @@ func (r *ledgerLogReadonly) GetPurgedVolumes() TouchedVolumeListReader { return NewTouchedVolumeListReader((*LedgerLog)(r).GetPurgedVolumes()) } +func (r *ledgerLogReadonly) GetNewVolumes() TouchedVolumeListReader { + return NewTouchedVolumeListReader((*LedgerLog)(r).GetNewVolumes()) +} + func (r *ledgerLogReadonly) Mutate() *LedgerLog { return (*LedgerLog)(r).CloneVT() } diff --git a/internal/proto/commonpb/common_vtproto.pb.go b/internal/proto/commonpb/common_vtproto.pb.go index 165fa61b93..723dc5dfe9 100644 --- a/internal/proto/commonpb/common_vtproto.pb.go +++ b/internal/proto/commonpb/common_vtproto.pb.go @@ -1802,6 +1802,13 @@ func (m *LedgerLog) CloneVT() *LedgerLog { } r.PurgedVolumes = tmpContainer } + if rhs := m.NewVolumes; rhs != nil { + tmpContainer := make([]*TouchedVolume, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.NewVolumes = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -7446,6 +7453,23 @@ func (this *LedgerLog) EqualVT(that *LedgerLog) bool { } } } + if len(this.NewVolumes) != len(that.NewVolumes) { + return false + } + for i, vx := range this.NewVolumes { + vy := that.NewVolumes[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &TouchedVolume{} + } + if q == nil { + q = &TouchedVolume{} + } + if !p.EqualVT(q) { + return false + } + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -16479,6 +16503,18 @@ func (m *LedgerLog) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.NewVolumes) > 0 { + for iNdEx := len(m.NewVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.NewVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + } if len(m.PurgedVolumes) > 0 { for iNdEx := len(m.PurgedVolumes) - 1; iNdEx >= 0; iNdEx-- { size, err := m.PurgedVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) @@ -24815,6 +24851,12 @@ func (m *LedgerLog) SizeVT() (n int) { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } } + if len(m.NewVolumes) > 0 { + for _, e := range m.NewVolumes { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } n += len(m.unknownFields) return n } @@ -38688,6 +38730,40 @@ func (m *LedgerLog) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewVolumes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewVolumes = append(m.NewVolumes, &TouchedVolume{}) + if err := m.NewVolumes[len(m.NewVolumes)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/internal/proto/raftcmdpb/raft_cmd.pb.go b/internal/proto/raftcmdpb/raft_cmd.pb.go index d70ef1a315..d3d0dccb7c 100644 --- a/internal/proto/raftcmdpb/raft_cmd.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd.pb.go @@ -4885,7 +4885,6 @@ type LedgerBoundaries struct { state protoimpl.MessageState `protogen:"open.v1"` NextTransactionId uint64 `protobuf:"fixed64,1,opt,name=next_transaction_id,json=nextTransactionId,proto3" json:"next_transaction_id,omitempty"` NextLogId uint64 `protobuf:"fixed64,2,opt,name=next_log_id,json=nextLogId,proto3" json:"next_log_id,omitempty"` - VolumeCount uint64 `protobuf:"fixed64,3,opt,name=volume_count,json=volumeCount,proto3" json:"volume_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4934,13 +4933,6 @@ func (x *LedgerBoundaries) GetNextLogId() uint64 { return 0 } -func (x *LedgerBoundaries) GetVolumeCount() uint64 { - if x != nil { - return x.VolumeCount - } - return 0 -} - type VolumePair struct { state protoimpl.MessageState `protogen:"open.v1"` Input *commonpb.Uint256 `protobuf:"bytes,1,opt,name=input,proto3" json:"input,omitempty"` @@ -6366,11 +6358,10 @@ const file_raft_cmd_proto_rawDesc = "" + "\vcreated_log\x18\x01 \x01(\v2\v.common.LogH\x00R\n" + "createdLog\x12/\n" + "\x12reference_sequence\x18\x02 \x01(\x06H\x00R\x11referenceSequenceB\x06\n" + - "\x04type\"\x85\x01\n" + + "\x04type\"b\n" + "\x10LedgerBoundaries\x12.\n" + "\x13next_transaction_id\x18\x01 \x01(\x06R\x11nextTransactionId\x12\x1e\n" + - "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\x12!\n" + - "\fvolume_count\x18\x03 \x01(\x06R\vvolumeCount\"\\\n" + + "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\"\\\n" + "\n" + "VolumePair\x12%\n" + "\x05input\x18\x01 \x01(\v2\x0f.common.Uint256R\x05input\x12'\n" + diff --git a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go index 16be100d8c..c73179d979 100644 --- a/internal/proto/raftcmdpb/raft_cmd_reader.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_reader.pb.go @@ -5249,7 +5249,6 @@ func NewCreatedLogOrReferenceListReader(s []*CreatedLogOrReference) CreatedLogOr type LedgerBoundariesReader interface { GetNextTransactionId() uint64 GetNextLogId() uint64 - GetVolumeCount() uint64 Mutate() *LedgerBoundaries } @@ -5263,10 +5262,6 @@ func (r *ledgerBoundariesReadonly) GetNextLogId() uint64 { return (*LedgerBoundaries)(r).GetNextLogId() } -func (r *ledgerBoundariesReadonly) GetVolumeCount() uint64 { - return (*LedgerBoundaries)(r).GetVolumeCount() -} - func (r *ledgerBoundariesReadonly) Mutate() *LedgerBoundaries { return (*LedgerBoundaries)(r).CloneVT() } diff --git a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go index 93b7015712..95bc30001c 100644 --- a/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd_vtproto.pb.go @@ -1960,7 +1960,6 @@ func (m *LedgerBoundaries) CloneVT() *LedgerBoundaries { r := new(LedgerBoundaries) r.NextTransactionId = m.NextTransactionId r.NextLogId = m.NextLogId - r.VolumeCount = m.VolumeCount if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -5802,9 +5801,6 @@ func (this *LedgerBoundaries) EqualVT(that *LedgerBoundaries) bool { if this.NextLogId != that.NextLogId { return false } - if this.VolumeCount != that.VolumeCount { - return false - } return string(this.unknownFields) == string(that.unknownFields) } @@ -11054,12 +11050,6 @@ func (m *LedgerBoundaries) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.VolumeCount != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.VolumeCount)) - i-- - dAtA[i] = 0x19 - } if m.NextLogId != 0 { i -= 8 binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.NextLogId)) @@ -14325,9 +14315,6 @@ func (m *LedgerBoundaries) SizeVT() (n int) { if m.NextLogId != 0 { n += 9 } - if m.VolumeCount != 0 { - n += 9 - } n += len(m.unknownFields) return n } @@ -25495,16 +25482,6 @@ func (m *LedgerBoundaries) UnmarshalVT(dAtA []byte) error { } m.NextLogId = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) iNdEx += 8 - case 3: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field VolumeCount", wireType) - } - m.VolumeCount = 0 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF - } - m.VolumeCount = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/internal/storage/usagestore/keys.go b/internal/storage/usagestore/keys.go index 8e73447cd0..f310b70561 100644 --- a/internal/storage/usagestore/keys.go +++ b/internal/storage/usagestore/keys.go @@ -34,6 +34,7 @@ const ( CounterReference byte = 0x04 // reference_count — count CreateTransactionOrder with non-empty Reference CounterEphemeralEvicted byte = 0x05 // ephemeral_evicted_count — sum len(LedgerLog.PurgedVolumes) per log CounterTransientUsed byte = 0x06 // transient_used_count — sum len(AppliedProposal.TransientVolumes[ledger].Volumes) per proposal + CounterVolume byte = 0x07 // volume_count — sum len(LedgerLog.NewVolumes) - sum len(LedgerLog.PurgedVolumes) per log ) // ProgressKey returns the full key for the usagebuilder progress entry. diff --git a/misc/proto/common.proto b/misc/proto/common.proto index 2e01041951..de426007bb 100644 --- a/misc/proto/common.proto +++ b/misc/proto/common.proto @@ -646,6 +646,15 @@ message LedgerLog { // purged while another stays kept; tagging the account as a whole would // over-skip mappings for transactions that touched the kept asset. repeated TouchedVolume purged_volumes = 4; + // Volumes (account+asset) whose persistent entry was newly created (never + // seen in the attribute store before) by THIS log. Ephemeral volumes appear + // in BOTH new_volumes AND purged_volumes for the same log: they were + // persisted briefly then evicted after commit. Transient volumes (never + // persisted) are excluded by construction. The usagebuilder derives the + // VolumeCount projection as sum(new_volumes) - sum(purged_volumes) across + // the audit chain; the checker verifies the derived counter against the + // live attribute store. + repeated TouchedVolume new_volumes = 5; } // TouchedVolume identifies a (ledger-local) volume cell — an account paired diff --git a/misc/proto/raft_cmd.proto b/misc/proto/raft_cmd.proto index 5c03c20512..26b9bdf2c8 100644 --- a/misc/proto/raft_cmd.proto +++ b/misc/proto/raft_cmd.proto @@ -666,7 +666,6 @@ message CreatedLogOrReference { message LedgerBoundaries { fixed64 next_transaction_id = 1; fixed64 next_log_id = 2; - fixed64 volume_count = 3; } message VolumePair { From ddc316a04efdb8c0d4593f26d04938e97d2eaa3c Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 10:00:10 +0200 Subject: [PATCH 05/35] perf(EN-1422): split LedgerLog volume annotations into 3 disjoint lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pattern-flagged during self-review: workloads that create many ephemeral accounts (payout fan-out, escrow pass-throughs) pay 2× bytes on the log payload with the previous encoding — every ephemeral tuple appeared in BOTH `new_volumes` AND `purged_volumes` because the two lists overlapped by design on pure ephemeral (new + purged same log). Restructure the LedgerLog volume annotations as three disjoint sets: - `purged_volumes` (field 4) — narrowed to DRAINING only (was non-zero in Pebble, back to zero at commit, evicted). Previously carried draining + ephemeral. - `new_kept_volumes` (field 5, renamed from new_volumes) — new + kept (survives commit). Previously carried new_kept + ephemeral. - `ephemeral_volumes` (field 6, new) — pure ephemeral (created + purged same log). Previously duplicated across the other two lists. For a payout tx with 100 ephemeral escrow accounts + 100 new-kept destinations + N normal updates: Before: 100 (in new_volumes) + 100 (in purged_volumes) + 100 (in new_volumes) + N ≈ 300 + N tuples After: 100 (in ephemeral_volumes) + 100 (in new_kept_volumes) + N ≈ 200 + N tuples Exact saving = `len(pure_ephemeral)` per log. At 1000 payout tx/s with 100 ephemerals each, this is ~3 MB/s less WAL/audit growth. - `write_set_ephemeral_purge.go`: drop `makePurgedKeySet`, `buildPurgedByLog`, `purgedVolumeKey` — replaced by the generalised helpers below. - `write_set_new_volumes.go`: `isNewVolumeUpdate` predicate, `volumeSetKey` shared type, `makeNewKeptKeySet(kept)`, `splitPurged(purged) → (ephemeral, draining)`, and `buildTouchedByLog(slots, set)` — one intersection helper reused three times. - `write_set.go`: `WriteSet` gains `newKeptByLog` and `ephemeralByLog` alongside the existing `purgedByLog`. The createdLogs injection loop wires all three fields. - `logCounts` gains explicit `newKept` / `ephemeral` fields. - `applyVolumeDelta` simplified: `delta = newKept − purged` (both disjoint sets; ephemeral contributes +0 to VolumeCount and is not involved in the arithmetic). - `CounterEphemeralEvicted` semantic tightened to pure ephemeral only (previously counted draining evictions too — the name always implied "ephemeral" but the counter reflected both categories). `extractPurgedVolumes` now unions `PurgedVolumes ∪ EphemeralVolumes` since both categories are evicted from Pebble and need the same acct→tx skip treatment. `parsedLog` (protowire fast path) parses field 6 and exposes `GetEphemeralVolumes` alongside the existing `GetPurgedVolumes`. `compareExclusionProjections` accumulates both `PurgedVolumes` and `EphemeralVolumes` into the stored projection set. Semantics unchanged overall — the split is a pure encoding optimisation. - AGENTS.md invariant #6: volume-preload contract clause updated to mention the three-way split. - architecture.md: LedgerBoundaries snippet + note explaining the new encoding and its ephemeral-heavy motivation. --- AGENTS.md | 2 +- docs/technical/architecture/overview.md | 8 +- internal/application/check/checker.go | 15 +- .../indexbuilder/applied_proposal_sync.go | 32 +- .../indexbuilder/protowire_postings.go | 34 +- .../application/usagebuilder/process_audit.go | 46 +- internal/infra/state/write_set.go | 71 +- .../infra/state/write_set_ephemeral_purge.go | 81 - .../state/write_set_ephemeral_purge_test.go | 10 +- internal/infra/state/write_set_new_volumes.go | 110 +- internal/proto/commonpb/common.pb.go | 1390 ++++++++++------- internal/proto/commonpb/common_dethash.pb.go | 15 +- internal/proto/commonpb/common_reader.pb.go | 11 +- internal/proto/commonpb/common_vtproto.pb.go | 102 +- internal/storage/usagestore/keys.go | 4 +- misc/proto/common.proto | 42 +- 16 files changed, 1155 insertions(+), 818 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7a49a8299a..e84547c0e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ This document contains rules and conventions for AI agents working on this codeb 5. **Never delete cache entries outside of rotations** — Cache entries must only be evicted during generation rotations (Gen0 → Gen1 → discard). Deleting individual entries breaks the cache prediction mechanism (bloom filters, tombstones). 6. **Every FSM `Registry.X.Get(...)` must have a matching preload, declared by the component that proposes the command** — The FSM apply path reads from the in-memory cache; a cache miss turns the read into a silent no-op. Each component that emits a proposal (the metadata converter, the index builder, the cluster-config reconciler, the idempotency-eviction scheduler, the mirror worker, admission) is responsible for declaring its own `preload.Needs` covering every key its apply path will read. There is NO central proposal→needs registry — coupling the preload package to every proposal type creates a single point that easily falls behind. The component knows what it reads; the component declares it. The shared `proposeTechnical` helper takes a `*preload.Needs` parameter the caller fills in (pass nil or an empty `Needs` when the apply path has no cache-keyed reads, e.g. cluster config / idempotency eviction). The preload populates the cache via `MirrorPreload` with a fresh value read at propose time (Pebble fallback on cache miss), and `PredictedIndex` catches mutations between propose and apply. - **Volume preload is load-bearing for the `LedgerLog.new_volumes` projection.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, inflates `usagestore.CounterVolume`, and only surfaces when the checker's volume-count pass runs. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. + **Volume preload is load-bearing for the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` split.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, mis-routes the tuple between the `new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` per-log lists, and inflates `usagestore.CounterVolume`. Only the checker's volume-count pass would eventually surface the drift. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. 7. **Never silently skip a "should not happen" branch** — A branch that is reachable only if an invariant is violated (nil where the contract says non-nil, a state we believe unreachable, a cache miss after a guaranteed preload, etc.) MUST surface a loud signal: `return fmt.Errorf("invariant: ...")` so it bubbles up, or `assert.Unreachable(...)` for SUT-level invariants exercised under antithesis. A silent `return nil` / `continue` on these branches hides real bugs — particularly catastrophic in the FSM apply path, where a no-op desyncs nodes from each other. Branches that represent genuine runtime conditions (cache miss as an expected outcome, stale proposal, deleted entity) keep their soft `return nil`. The distinction is whether the case is *expected* (soft skip OK) or *impossible by design* (must fail loudly). The comment must say *why* the case is impossible so a reader can decide whether to add a hard fail or relax the rule. 8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus); extend the list as new persisted projections land. diff --git a/docs/technical/architecture/overview.md b/docs/technical/architecture/overview.md index db2bf44a62..4c83c00ec7 100644 --- a/docs/technical/architecture/overview.md +++ b/docs/technical/architecture/overview.md @@ -157,7 +157,13 @@ message LedgerBoundaries { } ``` -> Every per-ledger counter — postings, reverts, numscript executions, references, ephemeral evictions, transient volumes, **and volumes** — lives in the usagebuilder side-store (`internal/application/usagebuilder`, `internal/storage/usagestore`). All counters are derived from the audit chain: event counts (posting / revert / numscript-exec / reference) come from replaying orders; ephemeral / transient counts come from `LedgerLog.PurgedVolumes` and `AppliedProposal.TransientVolumes`; the volume count comes from the FSM-enriched `LedgerLog.NewVolumes` field paired with `PurgedVolumes` (delta = new − purged). `LedgerBoundaries` carries only the two ID generators that drive apply-time allocation decisions. `metadata_count` is intentionally not exposed — the admission preload no longer injects old metadata values so cardinality cannot be reliably derived at the FSM level; it will come back on a sound foundation later. +> Every per-ledger counter — postings, reverts, numscript executions, references, ephemeral evictions, transient volumes, **and volumes** — lives in the usagebuilder side-store (`internal/application/usagebuilder`, `internal/storage/usagestore`). All counters are derived from the audit chain: event counts (posting / revert / numscript-exec / reference) come from replaying orders; transient counts come from `AppliedProposal.TransientVolumes`; ephemeral and volume counts come from three DISJOINT per-log lists that the FSM populates on every `LedgerLog`: +> +> - `purged_volumes` — draining evictions (was non-zero in Pebble, now zero, entry deleted at commit) +> - `new_kept_volumes` — persistent-new (created + survives past commit) +> - `ephemeral_volumes` — pure ephemeral (created + evicted same log) +> +> Volume count delta per log = `len(new_kept_volumes) − len(purged_volumes)`. Pure ephemeral tuples contribute +0 (was zero, is zero) and are tracked only for the index builder's skip logic and for the ephemeral-eviction counter. Splitting ephemeral out of `purged_volumes` avoids the 2× byte cost that a naive union-encoding would pay on ephemeral-heavy workloads. `LedgerBoundaries` carries only the two ID generators that drive apply-time allocation decisions. `metadata_count` is intentionally not exposed — the admission preload no longer injects old metadata values so cardinality cannot be reliably derived at the FSM level; it will come back on a sound foundation later. > **Note:** The `State` / `LedgerState` proto messages sometimes shown in older documentation are conceptual models, not actual proto definitions. The real FSM state is spread across the `Machine` struct fields and its `StateRegistry`. diff --git a/internal/application/check/checker.go b/internal/application/check/checker.go index 4aa5a08ec6..264a627612 100644 --- a/internal/application/check/checker.go +++ b/internal/application/check/checker.go @@ -431,13 +431,20 @@ func (c *Checker) Check(ctx context.Context, callback func(*servicepb.CheckStore checkReversionInvariants(ledgerName, seq, payload.Apply.GetLog().GetData(), ledgerKnownTxIDs, ledgerRevertedTxIDs, callback) - // Accumulate the LedgerLog.PurgedVolumes side of the - // stored projection while we have the log in hand; - // AppliedProposal.TransientVolumes is added in a - // single pass below. + // Accumulate the LedgerLog eviction lists (draining + // = PurgedVolumes, pure ephemeral = EphemeralVolumes) + // into the stored projection while we have the log + // in hand; AppliedProposal.TransientVolumes is added + // in a single pass below. Both eviction lists share + // the same "excluded from normal state" semantics + // — the split exists to compact log payloads on + // ephemeral-heavy workloads (EN-1422). for _, v := range payload.Apply.GetLog().GetPurgedVolumes() { addStored(ledgerName, v.GetAccount(), v.GetAsset()) } + for _, v := range payload.Apply.GetLog().GetEphemeralVolumes() { + addStored(ledgerName, v.GetAccount(), v.GetAsset()) + } } } } diff --git a/internal/application/indexbuilder/applied_proposal_sync.go b/internal/application/indexbuilder/applied_proposal_sync.go index 6643e6fa3b..81a71736e6 100644 --- a/internal/application/indexbuilder/applied_proposal_sync.go +++ b/internal/application/indexbuilder/applied_proposal_sync.go @@ -238,17 +238,39 @@ func (s *appliedProposalSync) close() error { return nil } -// extractPurgedVolumes returns the set of purged ephemeral (account, asset) -// volumes declared by THIS log. Empty when the order that produced the log -// did not contribute to any ephemeral purge. +// extractPurgedVolumes returns the set of (account, asset) volumes evicted +// from Pebble at commit by THIS log — the UNION of draining (PurgedVolumes) +// and pure ephemeral (EphemeralVolumes). Both categories share the same +// downstream treatment: their acct->tx mappings must be skipped because +// the volume entries no longer exist in the attribute store. +// +// Empty when the order that produced the log evicted nothing. func extractPurgedVolumes(ledgerLog ledgerLogWithPurgedVolumes) map[domain.AccountAssetKey]struct{} { - return domain.TouchedVolumeSet(ledgerLog.GetPurgedVolumes()) + purged := ledgerLog.GetPurgedVolumes() + ephemeral := ledgerLog.GetEphemeralVolumes() + + if len(purged) == 0 && len(ephemeral) == 0 { + return nil + } + + out := make(map[domain.AccountAssetKey]struct{}, len(purged)+len(ephemeral)) + for _, v := range purged { + out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset()}] = struct{}{} + } + for _, v := range ephemeral { + out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset()}] = struct{}{} + } + + return out } // ledgerLogWithPurgedVolumes narrows what we need from commonpb.LedgerLog so -// tests can pass either a real proto or a fake. +// tests can pass either a real proto or a fake. Covers the two on-log +// eviction lists (PurgedVolumes = draining, EphemeralVolumes = pure +// ephemeral). See LedgerLog proto docs for the disjoint-set invariant. type ledgerLogWithPurgedVolumes interface { GetPurgedVolumes() []*commonpb.TouchedVolume + GetEphemeralVolumes() []*commonpb.TouchedVolume } // excludedForLog returns the union of the transient (proposal-level) and diff --git a/internal/application/indexbuilder/protowire_postings.go b/internal/application/indexbuilder/protowire_postings.go index f93759bb8d..d3ff91e7c9 100644 --- a/internal/application/indexbuilder/protowire_postings.go +++ b/internal/application/indexbuilder/protowire_postings.go @@ -20,21 +20,24 @@ type rawPosting struct { // parsedLog holds the fields extracted by the protowire fast path. type parsedLog struct { - Sequence uint64 - Ledger string - TxID uint64 - Postings []rawPosting // reused across iterations via truncate-to-zero - LogType int32 // LedgerLogPayload oneof tag: 1=created, 2=reverted, 0=skip - PurgedVolumes []*commonpb.TouchedVolume // LedgerLog.purged_volumes (field 4), reused across iterations + Sequence uint64 + Ledger string + TxID uint64 + Postings []rawPosting // reused across iterations via truncate-to-zero + LogType int32 // LedgerLogPayload oneof tag: 1=created, 2=reverted, 0=skip + PurgedVolumes []*commonpb.TouchedVolume // LedgerLog.purged_volumes (field 4) — draining evictions, reused + EphemeralVolumes []*commonpb.TouchedVolume // LedgerLog.ephemeral_volumes (field 6) — pure ephemeral evictions, reused // DeletedLedger is the name carried by a DeleteLedger log (LogPayload // field 2); empty for every other log. It lets the backfill replay wipe // the deleted ledger's readstore rows, mirroring the live processLogs path. DeletedLedger string } -// GetPurgedVolumes satisfies ledgerLogWithPurgedVolumes so extractPurgedVolumes -// can consume the protowire fast path without going through commonpb. -func (p *parsedLog) GetPurgedVolumes() []*commonpb.TouchedVolume { return p.PurgedVolumes } +// GetPurgedVolumes / GetEphemeralVolumes satisfy ledgerLogWithPurgedVolumes +// so extractPurgedVolumes can consume the protowire fast path without going +// through commonpb. +func (p *parsedLog) GetPurgedVolumes() []*commonpb.TouchedVolume { return p.PurgedVolumes } +func (p *parsedLog) GetEphemeralVolumes() []*commonpb.TouchedVolume { return p.EphemeralVolumes } // parsePostingsFromLog extracts only the fields needed for posting indexation // from the raw bytes of a serialized Log message. It skips ~70% of the payload @@ -58,6 +61,7 @@ func parsePostingsFromLog(data []byte, out *parsedLog) error { out.Ledger = "" out.TxID = 0 out.PurgedVolumes = out.PurgedVolumes[:0] + out.EphemeralVolumes = out.EphemeralVolumes[:0] out.DeletedLedger = "" // --- Log level: extract sequence (field 1) and payload (field 2) --- @@ -207,6 +211,18 @@ func parsePostingsFromLog(data []byte, out *parsedLog) error { } out.PurgedVolumes = append(out.PurgedVolumes, vol) ledgerLogBytes = ledgerLogBytes[bn:] + case num == 6 && typ == protowire.BytesType: + b, bn := protowire.ConsumeBytes(ledgerLogBytes) + if bn < 0 { + return errors.New("protowire: invalid bytes for LedgerLog.ephemeral_volumes") + } + + vol, perr := parseTouchedVolume(b) + if perr != nil { + return fmt.Errorf("TouchedVolume: %w", perr) + } + out.EphemeralVolumes = append(out.EphemeralVolumes, vol) + ledgerLogBytes = ledgerLogBytes[bn:] default: n := protowire.ConsumeFieldValue(num, typ, ledgerLogBytes) if n < 0 { diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index b093040530..43c3bced3a 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -283,12 +283,14 @@ func (b *Builder) dispatchOrder( return nil } -// applyVolumeDelta feeds CounterVolume with the new-minus-purged delta from -// a single log. Ephemeral volumes cancel out because they appear in both -// lists — same log, contribution +0 net. This is the counter's cardinality -// semantics: sum(new_persistent) - sum(purged_persistent). +// applyVolumeDelta feeds CounterVolume with the new-kept minus draining +// delta from a single log. Pure ephemeral volumes live in their own +// EphemeralVolumes list on the log and contribute +0 to VolumeCount +// (was zero, is zero after commit — never counted). draining volumes +// (in PurgedVolumes) had a prior balance and are evicted from Pebble +// at commit, so they subtract from the live cardinality. func applyVolumeDelta(ledger string, counts logCounts, state *batchState) { - delta := counterDelta(counts.newVolumes) - counterDelta(counts.purged) + delta := counterDelta(counts.newKept) - counterDelta(counts.purged) if delta != 0 { state.addCounter(ledger, usagestore.CounterVolume, delta) } @@ -317,8 +319,8 @@ func (b *Builder) dispatchCreateTransaction( state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) } - if counts.purged > 0 { - state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) + if counts.ephemeral > 0 { + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.ephemeral)) } applyVolumeDelta(ledger, counts, state) @@ -362,8 +364,8 @@ func (b *Builder) dispatchRevertTransaction( state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) } - if counts.purged > 0 { - state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.purged)) + if counts.ephemeral > 0 { + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.ephemeral)) } applyVolumeDelta(ledger, counts, state) @@ -372,11 +374,14 @@ func (b *Builder) dispatchRevertTransaction( } // logCounts is the tuple of per-log counter deltas extracted from a single -// LedgerLog payload. +// LedgerLog payload. purged / newKept / ephemeral are the three disjoint +// volume-annotation lists on LedgerLog — see the proto comments for the +// invariants they satisfy. type logCounts struct { - postings int - purged int - newVolumes int + postings int + purged int // len(LedgerLog.PurgedVolumes) — draining only + newKept int // len(LedgerLog.NewKeptVolumes) — new + kept + ephemeral int // len(LedgerLog.EphemeralVolumes) — new + purged } // countsFromLog fetches the log at logSeq and returns the resolved posting @@ -403,13 +408,16 @@ func (b *Builder) countsFromLog(ctx context.Context, handle dal.PebbleGetter, lo return logCounts{}, nil } - // PurgedVolumes and NewVolumes live on LedgerLog directly (not on the - // payload variant), so we can extract them independently of the payload - // type. Ephemeral (account, asset) tuples appear in both lists — the - // caller cancels them out when computing the live VolumeCount delta. + // PurgedVolumes / NewKeptVolumes / EphemeralVolumes live on LedgerLog + // directly (not on the payload variant), so we can extract them + // independently of the payload type. The three lists are DISJOINT: pure + // ephemeral tuples appear only in EphemeralVolumes (not duplicated + // across Purged + New), keeping the log payload compact on + // ephemeral-heavy workloads. result := logCounts{ - purged: len(ledgerLog.GetPurgedVolumes()), - newVolumes: len(ledgerLog.GetNewVolumes()), + purged: len(ledgerLog.GetPurgedVolumes()), + newKept: len(ledgerLog.GetNewKeptVolumes()), + ephemeral: len(ledgerLog.GetEphemeralVolumes()), } if ledgerLog.GetData() == nil { diff --git a/internal/infra/state/write_set.go b/internal/infra/state/write_set.go index 4320d9e9ce..05587b487b 100644 --- a/internal/infra/state/write_set.go +++ b/internal/infra/state/write_set.go @@ -96,20 +96,26 @@ type WriteSet struct { transientVolumes map[string][]*commonpb.TouchedVolume // purgedByLog[i] is the deduplicated list of (account, asset) volumes - // that the log produced by order i touched and that the proposal-level - // partitionVolumes classified as purged. Computed during Merge from - // volumes.Slots() ∩ partResult.purged. Injected into each + // drained to zero (had a prior non-zero balance, now zero and evicted + // from Pebble) by the log produced by order i. DISJOINT from + // newKeptByLog and ephemeralByLog. Injected into each // LedgerLog.purged_volumes before AppendLogs. purgedByLog [][]*commonpb.TouchedVolume - // newByLog[i] is the deduplicated list of (account, asset) volumes - // whose persistent entry was newly created by the log produced by - // order i. Computed during Merge from volumes.Slots() intersected - // with the set of persistent updates whose preloaded prior value was - // undefined or the zero placeholder. Injected into each - // LedgerLog.new_volumes before AppendLogs. Feeds the usagebuilder's - // VolumeCount projection — see EN-1422. - newByLog [][]*commonpb.TouchedVolume + // newKeptByLog[i] is the deduplicated list of (account, asset) volumes + // whose persistent entry was newly created by order i AND survived + // past commit. DISJOINT from purgedByLog and ephemeralByLog. Injected + // into each LedgerLog.new_kept_volumes before AppendLogs. Feeds the + // usagebuilder's VolumeCount projection. + newKeptByLog [][]*commonpb.TouchedVolume + + // ephemeralByLog[i] is the deduplicated list of (account, asset) + // volumes that were both newly created AND purged by order i — pure + // ephemeral. DISJOINT from purgedByLog and newKeptByLog. Injected + // into each LedgerLog.ephemeral_volumes before AppendLogs. Consumed + // by the index builder (skip acct->tx mappings) alongside + // purgedByLog; contributes 0 to VolumeCount. + ephemeralByLog [][]*commonpb.TouchedVolume // bloomUpdates collects canonical keys per attribute type during Merge // for bloom filter updates before batch.Commit(). @@ -412,20 +418,23 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create } // Build createdLogs (skipping idempotency replays) and inject the - // per-log purged_volumes + new_volumes subsets before persisting. - // purgedByLog and newByLog are indexed by ORDER index — same as - // logsOrRefs — so the mapping uses the loop's i, not the createdLogs - // append index. - purgedSet := makePurgedKeySet(partResult.purged) - b.purgedByLog = buildPurgedByLog(b.volumes.Slots(), purgedSet) - - // New persistent volumes = kept + purged filtered on "prior value - // undefined or zero placeholder". Transient volumes are excluded by - // construction — they never reach the attribute store. The - // usagebuilder computes CounterVolume = sum(new_volumes) - sum(purged_volumes) - // across the audit chain, so ephemeral volumes contribute +0 net. - newSet := makeNewKeySet(partResult.kept, partResult.purged) - b.newByLog = buildNewByLog(b.volumes.Slots(), newSet) + // per-log volume annotation lists before persisting. Three disjoint + // sets — draining-purged, new-kept, and pure-ephemeral — are + // intersected with each order's touched volume slots to produce the + // per-log subsets. Order-indexed so the mapping uses the loop's i, + // not the createdLogs append index. + // + // The three-way split (vs. the earlier two-list encoding that + // duplicated ephemeral tuples across new_volumes and purged_volumes) + // keeps ephemeral-heavy workloads from paying 2× bytes on the log + // payload — see EN-1422. + ephemeralSet, drainingSet := splitPurged(partResult.purged) + newKeptSet := makeNewKeptKeySet(partResult.kept) + + slots := b.volumes.Slots() + b.purgedByLog = buildTouchedByLog(slots, drainingSet) + b.newKeptByLog = buildTouchedByLog(slots, newKeptSet) + b.ephemeralByLog = buildTouchedByLog(slots, ephemeralSet) createdLogs := make([]*commonpb.Log, 0, len(logsOrRefs)) for i, lr := range logsOrRefs { @@ -438,9 +447,10 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create } hasPurged := i < len(b.purgedByLog) && len(b.purgedByLog[i]) > 0 - hasNew := i < len(b.newByLog) && len(b.newByLog[i]) > 0 + hasNewKept := i < len(b.newKeptByLog) && len(b.newKeptByLog[i]) > 0 + hasEphemeral := i < len(b.ephemeralByLog) && len(b.ephemeralByLog[i]) > 0 - if hasPurged || hasNew { + if hasPurged || hasNewKept || hasEphemeral { apply := log.GetPayload().GetApply() if apply == nil { return fmt.Errorf("invariant: order %d produced volume annotations but its log payload is not an ApplyLedgerLog (payload=%T)", @@ -453,8 +463,11 @@ func (b *WriteSet) Merge(batch *dal.WriteSession, logsOrRefs []*raftcmdpb.Create if hasPurged { ledgerLog.PurgedVolumes = b.purgedByLog[i] } - if hasNew { - ledgerLog.NewVolumes = b.newByLog[i] + if hasNewKept { + ledgerLog.NewKeptVolumes = b.newKeptByLog[i] + } + if hasEphemeral { + ledgerLog.EphemeralVolumes = b.ephemeralByLog[i] } } diff --git a/internal/infra/state/write_set_ephemeral_purge.go b/internal/infra/state/write_set_ephemeral_purge.go index 7f2391c607..6aad43741b 100644 --- a/internal/infra/state/write_set_ephemeral_purge.go +++ b/internal/infra/state/write_set_ephemeral_purge.go @@ -1,8 +1,6 @@ package state import ( - "sort" - "github.com/formancehq/ledger/v3/internal/domain" "github.com/formancehq/ledger/v3/internal/domain/accounttype" "github.com/formancehq/ledger/v3/internal/infra/attributes" @@ -117,85 +115,6 @@ func (b *WriteSet) partitionVolumes( return result } -// makePurgedKeySet builds a lookup set over the (ledger, account, asset) -// of every purged volume entry. Keeping the asset dimension matters: a -// multi-asset account may have one asset purged while another stays kept — -// dropping the asset would over-attribute purged state to orders touching -// the still-kept asset. -func makePurgedKeySet(purged []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) map[purgedVolumeKey]struct{} { - if len(purged) == 0 { - return nil - } - set := make(map[purgedVolumeKey]struct{}, len(purged)) - for _, u := range purged { - set[purgedVolumeKey{Ledger: u.Key.LedgerName, Account: u.Key.Account, Asset: u.Key.Asset}] = struct{}{} - } - - return set -} - -// purgedVolumeKey is the (ledger, account, asset) tuple used by -// makePurgedKeySet and buildPurgedByLog. The asset dimension is kept to -// avoid over-attribution in multi-asset accounts. -type purgedVolumeKey struct { - Ledger string - Account string - Asset string -} - -// buildPurgedByLog produces, for each order index, the deduplicated list of -// (account, asset) tuples that the order touched and that the proposal -// classified as purged. Indexed by order_index; entries for orders that -// touched nothing purged (or didn't touch volumes at all) are nil. Tuples -// within an entry are sorted (by account then asset) to keep the log payload -// deterministic across runs. -func buildPurgedByLog(perOrderVolumeKeys [][]domain.VolumeKey, purged map[purgedVolumeKey]struct{}) [][]*commonpb.TouchedVolume { - if len(perOrderVolumeKeys) == 0 || len(purged) == 0 { - return nil - } - - type accAsset struct{ Account, Asset string } - - out := make([][]*commonpb.TouchedVolume, len(perOrderVolumeKeys)) - for i, keys := range perOrderVolumeKeys { - if len(keys) == 0 { - continue - } - - seen := make(map[accAsset]struct{}, len(keys)) - for _, k := range keys { - if _, ok := purged[purgedVolumeKey{Ledger: k.LedgerName, Account: k.Account, Asset: k.Asset}]; !ok { - continue - } - seen[accAsset{Account: k.Account, Asset: k.Asset}] = struct{}{} - } - - if len(seen) == 0 { - continue - } - - ordered := make([]accAsset, 0, len(seen)) - for k := range seen { - ordered = append(ordered, k) - } - sort.Slice(ordered, func(a, b int) bool { - if ordered[a].Account != ordered[b].Account { - return ordered[a].Account < ordered[b].Account - } - - return ordered[a].Asset < ordered[b].Asset - }) - - vols := make([]*commonpb.TouchedVolume, len(ordered)) - for j, k := range ordered { - vols[j] = &commonpb.TouchedVolume{Account: k.Account, Asset: k.Asset} - } - out[i] = vols - } - - return out -} - // applyEphemeralPurge deletes purged volumes from 0xF1 then zeroes the cache. // Deleting saves storage; the cache is zeroed (rather than deleted) so any // co-batched proposal admitted with CacheHit still sees a populated diff --git a/internal/infra/state/write_set_ephemeral_purge_test.go b/internal/infra/state/write_set_ephemeral_purge_test.go index cab4f1f301..f703ffd6b7 100644 --- a/internal/infra/state/write_set_ephemeral_purge_test.go +++ b/internal/infra/state/write_set_ephemeral_purge_test.go @@ -443,20 +443,20 @@ func TestBuildPurgedByLog_KeepsAssetDimension(t *testing.T) { account := "ephemeral:multi" // purged set contains only (account, USD) — the EUR cell is kept. - purged := map[purgedVolumeKey]struct{}{ + purged := map[volumeSetKey]struct{}{ {Ledger: ledger, Account: account, Asset: "USD"}: {}, } - // Order 0 touches (account, EUR) — must NOT appear in purgedByLog. - // Order 1 touches (account, USD) — must appear in purgedByLog[1]. + // Order 0 touches (account, EUR) — must NOT appear in the output. + // Order 1 touches (account, USD) — must appear in out[1]. perOrderVolumeKeys := [][]domain.VolumeKey{ {{AccountKey: domain.AccountKey{LedgerName: ledger, Account: account}, Asset: "EUR"}}, {{AccountKey: domain.AccountKey{LedgerName: ledger, Account: account}, Asset: "USD"}}, } - out := buildPurgedByLog(perOrderVolumeKeys, purged) + out := buildTouchedByLog(perOrderVolumeKeys, purged) require.Len(t, out, 2) - require.Empty(t, out[0], "order touching only kept assets must not be flagged purged") + require.Empty(t, out[0], "order touching only kept assets must not appear in the touched set") require.Len(t, out[1], 1) require.Equal(t, account, out[1][0].GetAccount()) require.Equal(t, "USD", out[1][0].GetAsset()) diff --git a/internal/infra/state/write_set_new_volumes.go b/internal/infra/state/write_set_new_volumes.go index 5e1ce9623a..a7ebc0da2f 100644 --- a/internal/infra/state/write_set_new_volumes.go +++ b/internal/infra/state/write_set_new_volumes.go @@ -26,68 +26,84 @@ func isVolumePreloadZero(v *raftcmdpb.VolumePair) bool { (out == nil || (out.GetV0() == 0 && out.GetV1() == 0 && out.GetV2() == 0 && out.GetV3() == 0)) } -// newPersistentVolumeKey is the (ledger, account, asset) tuple used by makeNewKeySet -// and buildNewByLog. Mirrors purgedVolumeKey — see write_set_ephemeral_purge.go -// for the asset-dimension rationale. -type newPersistentVolumeKey struct { +// isNewVolumeUpdate reports whether a volume update represents a +// first-time write to that (account, asset) key. "New" is defined by the +// preloaded prior value: absent or the zero placeholder → new; a defined +// non-zero prior value → pre-existing. +func isNewVolumeUpdate(u attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) bool { + if !u.Old.IsDefined() { + return true + } + + return isVolumePreloadZero(u.Old.Value()) +} + +// volumeSetKey is the (ledger, account, asset) tuple used by the per-log +// intersection helpers below. Mirrors purgedVolumeKey's asset-dimension +// rationale — a multi-asset account may split across categories. +type volumeSetKey struct { Ledger string Account string Asset string } -// makeNewKeySet builds a lookup set over the (ledger, account, asset) of every -// PERSISTENT volume that was newly created by this proposal. A volume is -// "new" iff its preloaded prior value was either undefined or the zero -// placeholder — i.e. the attribute store did not yet carry an entry for that -// (account, asset) tuple. -// -// Only persistent updates (kept + ephemeral) qualify. Transient volumes never -// hit the attribute store, so they are never "new" in the persisted sense — -// the caller passes only persistent slices. -func makeNewKeySet(persistentUpdates ...[]attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) map[newPersistentVolumeKey]struct{} { - total := 0 - for _, updates := range persistentUpdates { - total += len(updates) - } +// makeNewKeptKeySet builds the set of (ledger, account, asset) tuples that +// were newly created AND survived past commit — i.e. persistent-new volumes +// that are NOT ephemeral. Consumed by buildNewKeptByLog. +func makeNewKeptKeySet(kept []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) map[volumeSetKey]struct{} { + set := make(map[volumeSetKey]struct{}) - if total == 0 { - return nil + for i := range kept { + if !isNewVolumeUpdate(kept[i]) { + continue + } + + set[volumeSetKey{ + Ledger: kept[i].Key.LedgerName, + Account: kept[i].Key.Account, + Asset: kept[i].Key.Asset, + }] = struct{}{} } - set := make(map[newPersistentVolumeKey]struct{}, total) + return set +} - for _, updates := range persistentUpdates { - for i := range updates { - old := updates[i].Old - if old.IsDefined() && !isVolumePreloadZero(old.Value()) { - continue - } +// splitPurged partitions partResult.purged into pure-ephemeral (was zero, +// briefly touched, is zero at commit) and draining (was non-zero, back to +// zero). The two sets are disjoint by definition — a purged update either +// had a prior balance or did not. +func splitPurged(purged []attributes.Update[domain.VolumeKey, *raftcmdpb.VolumePair]) (ephemeral, draining map[volumeSetKey]struct{}) { + ephemeral = make(map[volumeSetKey]struct{}) + draining = make(map[volumeSetKey]struct{}) + + for i := range purged { + key := volumeSetKey{ + Ledger: purged[i].Key.LedgerName, + Account: purged[i].Key.Account, + Asset: purged[i].Key.Asset, + } - set[newPersistentVolumeKey{ - Ledger: updates[i].Key.LedgerName, - Account: updates[i].Key.Account, - Asset: updates[i].Key.Asset, - }] = struct{}{} + if isNewVolumeUpdate(purged[i]) { + ephemeral[key] = struct{}{} + } else { + draining[key] = struct{}{} } } - return set + return ephemeral, draining } -// buildNewByLog produces, for each order index, the deduplicated list of -// (account, asset) tuples that the order touched and that the proposal -// classified as newly-created persistent volumes. Indexed by order_index; -// entries for orders that produced no new persistent volume are nil. Tuples -// within an entry are sorted (by account then asset) to keep the log payload -// deterministic across runs. +// buildTouchedByLog produces, for each order index, the deduplicated list of +// (account, asset) tuples the order touched that fall in the given set. +// Indexed by order_index; entries for orders with no matching keys are nil. +// Tuples within an entry are sorted (by account then asset) so the log +// payload is deterministic across nodes and runs. // -// Semantically parallel to buildPurgedByLog. Ephemeral volumes appear in -// BOTH new_volumes AND purged_volumes for the same log: they were persisted -// (hence "new") and evicted after commit (hence "purged"). The usagebuilder's -// VolumeCount = sum(new_volumes) - sum(purged_volumes) relies on that -// symmetry. -func buildNewByLog(perOrderVolumeKeys [][]domain.VolumeKey, newSet map[newPersistentVolumeKey]struct{}) [][]*commonpb.TouchedVolume { - if len(perOrderVolumeKeys) == 0 || len(newSet) == 0 { +// This is the generalisation of buildPurgedByLog / buildNewByLog into one +// helper — the caller supplies the intersection set (draining, ephemeral, +// or new-kept). +func buildTouchedByLog(perOrderVolumeKeys [][]domain.VolumeKey, set map[volumeSetKey]struct{}) [][]*commonpb.TouchedVolume { + if len(perOrderVolumeKeys) == 0 || len(set) == 0 { return nil } @@ -101,7 +117,7 @@ func buildNewByLog(perOrderVolumeKeys [][]domain.VolumeKey, newSet map[newPersis seen := make(map[accAsset]struct{}, len(keys)) for _, k := range keys { - if _, ok := newSet[newPersistentVolumeKey{Ledger: k.LedgerName, Account: k.Account, Asset: k.Asset}]; !ok { + if _, ok := set[volumeSetKey{Ledger: k.LedgerName, Account: k.Account, Asset: k.Asset}]; !ok { continue } seen[accAsset{Account: k.Account, Asset: k.Asset}] = struct{}{} diff --git a/internal/proto/commonpb/common.pb.go b/internal/proto/commonpb/common.pb.go index 0f300d090e..0f4a845985 100644 --- a/internal/proto/commonpb/common.pb.go +++ b/internal/proto/commonpb/common.pb.go @@ -947,6 +947,77 @@ func (AccountTypePersistence) EnumDescriptor() ([]byte, []int) { return file_common_proto_rawDescGZIP(), []int{13} } +// AuditField enumerates the audit fields that can be filtered. The set is +// intentionally limited to what the audit access path can resolve efficiently +// (index lookup or key-range bound); NOT / non-indexed fields are rejected +// rather than degrading to a full-chain scan. +type AuditField int32 + +const ( + AuditField_AUDIT_FIELD_UNSPECIFIED AuditField = 0 + AuditField_AUDIT_FIELD_SEQUENCE AuditField = 1 // uint -> AuditEntry.sequence (audit-zone key range) + AuditField_AUDIT_FIELD_PROPOSAL_ID AuditField = 2 // uint -> AuditEntry.proposal_id (index range) + AuditField_AUDIT_FIELD_TIMESTAMP AuditField = 3 // uint -> AuditEntry.timestamp.data, unix micros (index range) + AuditField_AUDIT_FIELD_LOG_SEQUENCE AuditField = 4 // uint -> item log_sequence, match-any (index range) + AuditField_AUDIT_FIELD_OUTCOME AuditField = 5 // string in {success, failure} (index) + AuditField_AUDIT_FIELD_CALLER_SUBJECT AuditField = 6 // string -> caller_snapshot.identity.subject (index) + AuditField_AUDIT_FIELD_LEDGER AuditField = 7 // string -> AuditEntry.ledgers, match-any (index) + AuditField_AUDIT_FIELD_ORDER_TYPE AuditField = 8 // string -> order payload variant, match-any (index) +) + +// Enum value maps for AuditField. +var ( + AuditField_name = map[int32]string{ + 0: "AUDIT_FIELD_UNSPECIFIED", + 1: "AUDIT_FIELD_SEQUENCE", + 2: "AUDIT_FIELD_PROPOSAL_ID", + 3: "AUDIT_FIELD_TIMESTAMP", + 4: "AUDIT_FIELD_LOG_SEQUENCE", + 5: "AUDIT_FIELD_OUTCOME", + 6: "AUDIT_FIELD_CALLER_SUBJECT", + 7: "AUDIT_FIELD_LEDGER", + 8: "AUDIT_FIELD_ORDER_TYPE", + } + AuditField_value = map[string]int32{ + "AUDIT_FIELD_UNSPECIFIED": 0, + "AUDIT_FIELD_SEQUENCE": 1, + "AUDIT_FIELD_PROPOSAL_ID": 2, + "AUDIT_FIELD_TIMESTAMP": 3, + "AUDIT_FIELD_LOG_SEQUENCE": 4, + "AUDIT_FIELD_OUTCOME": 5, + "AUDIT_FIELD_CALLER_SUBJECT": 6, + "AUDIT_FIELD_LEDGER": 7, + "AUDIT_FIELD_ORDER_TYPE": 8, + } +) + +func (x AuditField) Enum() *AuditField { + p := new(AuditField) + *p = x + return p +} + +func (x AuditField) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AuditField) Descriptor() protoreflect.EnumDescriptor { + return file_common_proto_enumTypes[14].Descriptor() +} + +func (AuditField) Type() protoreflect.EnumType { + return &file_common_proto_enumTypes[14] +} + +func (x AuditField) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AuditField.Descriptor instead. +func (AuditField) EnumDescriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{14} +} + type AddressRole int32 const ( @@ -980,11 +1051,11 @@ func (x AddressRole) String() string { } func (AddressRole) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[14].Descriptor() + return file_common_proto_enumTypes[15].Descriptor() } func (AddressRole) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[14] + return &file_common_proto_enumTypes[15] } func (x AddressRole) Number() protoreflect.EnumNumber { @@ -993,7 +1064,7 @@ func (x AddressRole) Number() protoreflect.EnumNumber { // Deprecated: Use AddressRole.Descriptor instead. func (AddressRole) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{14} + return file_common_proto_rawDescGZIP(), []int{15} } type QueryTarget int32 @@ -1029,11 +1100,11 @@ func (x QueryTarget) String() string { } func (QueryTarget) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[15].Descriptor() + return file_common_proto_enumTypes[16].Descriptor() } func (QueryTarget) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[15] + return &file_common_proto_enumTypes[16] } func (x QueryTarget) Number() protoreflect.EnumNumber { @@ -1042,7 +1113,7 @@ func (x QueryTarget) Number() protoreflect.EnumNumber { // Deprecated: Use QueryTarget.Descriptor instead. func (QueryTarget) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{15} + return file_common_proto_rawDescGZIP(), []int{16} } type QueryMode int32 @@ -1075,11 +1146,11 @@ func (x QueryMode) String() string { } func (QueryMode) Descriptor() protoreflect.EnumDescriptor { - return file_common_proto_enumTypes[16].Descriptor() + return file_common_proto_enumTypes[17].Descriptor() } func (QueryMode) Type() protoreflect.EnumType { - return &file_common_proto_enumTypes[16] + return &file_common_proto_enumTypes[17] } func (x QueryMode) Number() protoreflect.EnumNumber { @@ -1088,7 +1159,7 @@ func (x QueryMode) Number() protoreflect.EnumNumber { // Deprecated: Use QueryMode.Descriptor instead. func (QueryMode) EnumDescriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{16} + return file_common_proto_rawDescGZIP(), []int{17} } type Timestamp struct { @@ -5677,27 +5748,33 @@ type LedgerLog struct { Data *LedgerLogPayload `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` Date *Timestamp `protobuf:"bytes,2,opt,name=date,proto3" json:"date,omitempty"` Id uint64 `protobuf:"fixed64,3,opt,name=id,proto3" json:"id,omitempty"` - // Volumes (account+asset) whose ephemeral state was purged (zero balance) - // by THIS log specifically. Subset of the proposal's purged universe — - // only volumes touched by the order that produced this log. Empty when - // the order did not contribute to any ephemeral purge. The index builder - // uses this to skip account->transaction mappings for the corresponding - // (account, asset) tuples without reading the audit zone. Carrying the - // asset dimension matters: a multi-asset account may have one asset - // purged while another stays kept; tagging the account as a whole would - // over-skip mappings for transactions that touched the kept asset. + // Volumes (account+asset) DRAINED to zero by THIS log — the entry had a + // pre-existing non-zero balance in Pebble and this log brought it back to + // zero, causing the volume to be evicted from the attribute store at + // commit. Purged is DISJOINT from ephemeral_volumes and new_kept_volumes. + // The index builder skips account->transaction mappings for + // purged ∪ ephemeral (both are evicted from Pebble). The usagebuilder + // subtracts len(purged_volumes) from VolumeCount — a draining eviction + // decreases the live cardinality by one. PurgedVolumes []*TouchedVolume `protobuf:"bytes,4,rep,name=purged_volumes,json=purgedVolumes,proto3" json:"purged_volumes,omitempty"` - // Volumes (account+asset) whose persistent entry was newly created (never - // seen in the attribute store before) by THIS log. Ephemeral volumes appear - // in BOTH new_volumes AND purged_volumes for the same log: they were - // persisted briefly then evicted after commit. Transient volumes (never - // persisted) are excluded by construction. The usagebuilder derives the - // VolumeCount projection as sum(new_volumes) - sum(purged_volumes) across - // the audit chain; the checker verifies the derived counter against the - // live attribute store. - NewVolumes []*TouchedVolume `protobuf:"bytes,5,rep,name=new_volumes,json=newVolumes,proto3" json:"new_volumes,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Volumes (account+asset) whose PERSISTENT entry was newly created by + // THIS log AND SURVIVED past commit. New + kept — disjoint from + // purged_volumes and ephemeral_volumes. The usagebuilder increments + // VolumeCount by len(new_kept_volumes) — a newly-persisted key raises + // the live cardinality by one. + NewKeptVolumes []*TouchedVolume `protobuf:"bytes,5,rep,name=new_kept_volumes,json=newKeptVolumes,proto3" json:"new_kept_volumes,omitempty"` + // Volumes (account+asset) that were both newly created AND purged by + // THIS log — pure ephemeral. Written to Pebble briefly then evicted at + // commit. Contributes +0 to VolumeCount (was zero, is zero after commit) + // and is tracked separately so: + // - the index builder can skip acct->tx mappings for them (via + // purged_volumes ∪ ephemeral_volumes); + // - the log payload does not duplicate ephemeral tuples in two lists + // (previous encoding carried them in both purged_volumes AND + // new_volumes, doubling bytes on ephemeral-heavy workloads). + EphemeralVolumes []*TouchedVolume `protobuf:"bytes,6,rep,name=ephemeral_volumes,json=ephemeralVolumes,proto3" json:"ephemeral_volumes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LedgerLog) Reset() { @@ -5758,9 +5835,16 @@ func (x *LedgerLog) GetPurgedVolumes() []*TouchedVolume { return nil } -func (x *LedgerLog) GetNewVolumes() []*TouchedVolume { +func (x *LedgerLog) GetNewKeptVolumes() []*TouchedVolume { if x != nil { - return x.NewVolumes + return x.NewKeptVolumes + } + return nil +} + +func (x *LedgerLog) GetEphemeralVolumes() []*TouchedVolume { + if x != nil { + return x.EphemeralVolumes } return nil } @@ -9852,6 +9936,7 @@ type QueryFilter struct { // *QueryFilter_LogBuiltinUint // *QueryFilter_AccountHasAsset // *QueryFilter_Reverted + // *QueryFilter_Audit Filter isQueryFilter_Filter `protobuf_oneof:"filter"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -10002,6 +10087,15 @@ func (x *QueryFilter) GetReverted() *RevertedCondition { return nil } +func (x *QueryFilter) GetAudit() *AuditCondition { + if x != nil { + if x, ok := x.Filter.(*QueryFilter_Audit); ok { + return x.Audit + } + } + return nil +} + type isQueryFilter_Filter interface { isQueryFilter_Filter() } @@ -10054,6 +10148,10 @@ type QueryFilter_Reverted struct { Reverted *RevertedCondition `protobuf:"bytes,12,opt,name=reverted,proto3,oneof"` } +type QueryFilter_Audit struct { + Audit *AuditCondition `protobuf:"bytes,13,opt,name=audit,proto3,oneof"` +} + func (*QueryFilter_Field) isQueryFilter_Filter() {} func (*QueryFilter_Address) isQueryFilter_Filter() {} @@ -10078,6 +10176,8 @@ func (*QueryFilter_AccountHasAsset) isQueryFilter_Filter() {} func (*QueryFilter_Reverted) isQueryFilter_Filter() {} +func (*QueryFilter_Audit) isQueryFilter_Filter() {} + // ReferenceCondition filters transactions by reference (exact match). type ReferenceCondition struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -10170,6 +10270,110 @@ func (x *RevertedCondition) GetValue() bool { return false } +// AuditCondition filters audit entries by a single audit field, mirroring +// BuiltinUintCondition's (field-enum × condition) shape. It is only meaningful +// for the audit list path: the audit compiler (query.CompileAuditFilter) accepts +// Audit/And/Or leaves and rejects every other QueryFilter variant with +// InvalidArgument, while the index compiler used for accounts/transactions/logs +// (query.Compile) never lists QueryFilter_Audit and rejects it there too. There +// is no QueryTarget dispatch for audit — ListAuditEntries calls +// CompileAuditFilter directly, so no QUERY_TARGET_AUDIT enum value is needed. +// +// Every exposed field is answerable from the readstore audit secondary index +// (EN-1339) — outcome, ledger, caller_subject, order_type, timestamp, +// proposal_id, log_seq — except AUDIT_FIELD_SEQUENCE, which is the audit-zone +// key itself and is served by bounding the entry scan. There is deliberately no +// scan-time predicate fallback: a field the index cannot answer is not exposed. +type AuditCondition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Field AuditField `protobuf:"varint,1,opt,name=field,proto3,enum=common.AuditField" json:"field,omitempty"` + // Types that are valid to be assigned to Condition: + // + // *AuditCondition_StringCond + // *AuditCondition_UintCond + Condition isAuditCondition_Condition `protobuf_oneof:"condition"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AuditCondition) Reset() { + *x = AuditCondition{} + mi := &file_common_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuditCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuditCondition) ProtoMessage() {} + +func (x *AuditCondition) ProtoReflect() protoreflect.Message { + mi := &file_common_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuditCondition.ProtoReflect.Descriptor instead. +func (*AuditCondition) Descriptor() ([]byte, []int) { + return file_common_proto_rawDescGZIP(), []int{126} +} + +func (x *AuditCondition) GetField() AuditField { + if x != nil { + return x.Field + } + return AuditField_AUDIT_FIELD_UNSPECIFIED +} + +func (x *AuditCondition) GetCondition() isAuditCondition_Condition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *AuditCondition) GetStringCond() *StringCondition { + if x != nil { + if x, ok := x.Condition.(*AuditCondition_StringCond); ok { + return x.StringCond + } + } + return nil +} + +func (x *AuditCondition) GetUintCond() *UintCondition { + if x != nil { + if x, ok := x.Condition.(*AuditCondition_UintCond); ok { + return x.UintCond + } + } + return nil +} + +type isAuditCondition_Condition interface { + isAuditCondition_Condition() +} + +type AuditCondition_StringCond struct { + StringCond *StringCondition `protobuf:"bytes,2,opt,name=string_cond,json=stringCond,proto3,oneof"` +} + +type AuditCondition_UintCond struct { + UintCond *UintCondition `protobuf:"bytes,3,opt,name=uint_cond,json=uintCond,proto3,oneof"` +} + +func (*AuditCondition_StringCond) isAuditCondition_Condition() {} + +func (*AuditCondition_UintCond) isAuditCondition_Condition() {} + // LedgerCondition filters logs by ledger name (exact match). type LedgerCondition struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -10180,7 +10384,7 @@ type LedgerCondition struct { func (x *LedgerCondition) Reset() { *x = LedgerCondition{} - mi := &file_common_proto_msgTypes[126] + mi := &file_common_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10192,7 +10396,7 @@ func (x *LedgerCondition) String() string { func (*LedgerCondition) ProtoMessage() {} func (x *LedgerCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[126] + mi := &file_common_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10205,7 +10409,7 @@ func (x *LedgerCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerCondition.ProtoReflect.Descriptor instead. func (*LedgerCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{126} + return file_common_proto_rawDescGZIP(), []int{127} } func (x *LedgerCondition) GetCond() *StringCondition { @@ -10225,7 +10429,7 @@ type LogIdCondition struct { func (x *LogIdCondition) Reset() { *x = LogIdCondition{} - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10237,7 +10441,7 @@ func (x *LogIdCondition) String() string { func (*LogIdCondition) ProtoMessage() {} func (x *LogIdCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[127] + mi := &file_common_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10250,7 +10454,7 @@ func (x *LogIdCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogIdCondition.ProtoReflect.Descriptor instead. func (*LogIdCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{127} + return file_common_proto_rawDescGZIP(), []int{128} } func (x *LogIdCondition) GetCond() *UintCondition { @@ -10260,7 +10464,7 @@ func (x *LogIdCondition) GetCond() *UintCondition { return nil } -// BuiltinUintCondition filters transactions by a built-in uint64 field (id, timestamp, or inserted_at). +// BuiltinUintCondition filters transactions by a built-in uint64 field (id, timestamp, inserted_at, or reverted_at). type BuiltinUintCondition struct { state protoimpl.MessageState `protogen:"open.v1"` Field TransactionBuiltinIndex `protobuf:"varint,1,opt,name=field,proto3,enum=common.TransactionBuiltinIndex" json:"field,omitempty"` @@ -10271,7 +10475,7 @@ type BuiltinUintCondition struct { func (x *BuiltinUintCondition) Reset() { *x = BuiltinUintCondition{} - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10283,7 +10487,7 @@ func (x *BuiltinUintCondition) String() string { func (*BuiltinUintCondition) ProtoMessage() {} func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[128] + mi := &file_common_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10296,7 +10500,7 @@ func (x *BuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BuiltinUintCondition.ProtoReflect.Descriptor instead. func (*BuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{128} + return file_common_proto_rawDescGZIP(), []int{129} } func (x *BuiltinUintCondition) GetField() TransactionBuiltinIndex { @@ -10324,7 +10528,7 @@ type LogBuiltinUintCondition struct { func (x *LogBuiltinUintCondition) Reset() { *x = LogBuiltinUintCondition{} - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10336,7 +10540,7 @@ func (x *LogBuiltinUintCondition) String() string { func (*LogBuiltinUintCondition) ProtoMessage() {} func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[129] + mi := &file_common_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10349,7 +10553,7 @@ func (x *LogBuiltinUintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use LogBuiltinUintCondition.ProtoReflect.Descriptor instead. func (*LogBuiltinUintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{129} + return file_common_proto_rawDescGZIP(), []int{130} } func (x *LogBuiltinUintCondition) GetField() LogBuiltinIndex { @@ -10380,7 +10584,7 @@ type AccountHasAssetCondition struct { func (x *AccountHasAssetCondition) Reset() { *x = AccountHasAssetCondition{} - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10392,7 +10596,7 @@ func (x *AccountHasAssetCondition) String() string { func (*AccountHasAssetCondition) ProtoMessage() {} func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[130] + mi := &file_common_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10405,7 +10609,7 @@ func (x *AccountHasAssetCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AccountHasAssetCondition.ProtoReflect.Descriptor instead. func (*AccountHasAssetCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{130} + return file_common_proto_rawDescGZIP(), []int{131} } func (x *AccountHasAssetCondition) GetAssetBase() string { @@ -10431,7 +10635,7 @@ type AndFilter struct { func (x *AndFilter) Reset() { *x = AndFilter{} - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10443,7 +10647,7 @@ func (x *AndFilter) String() string { func (*AndFilter) ProtoMessage() {} func (x *AndFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[131] + mi := &file_common_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10456,7 +10660,7 @@ func (x *AndFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use AndFilter.ProtoReflect.Descriptor instead. func (*AndFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{131} + return file_common_proto_rawDescGZIP(), []int{132} } func (x *AndFilter) GetFilters() []*QueryFilter { @@ -10475,7 +10679,7 @@ type OrFilter struct { func (x *OrFilter) Reset() { *x = OrFilter{} - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10487,7 +10691,7 @@ func (x *OrFilter) String() string { func (*OrFilter) ProtoMessage() {} func (x *OrFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[132] + mi := &file_common_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10500,7 +10704,7 @@ func (x *OrFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use OrFilter.ProtoReflect.Descriptor instead. func (*OrFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{132} + return file_common_proto_rawDescGZIP(), []int{133} } func (x *OrFilter) GetFilters() []*QueryFilter { @@ -10519,7 +10723,7 @@ type NotFilter struct { func (x *NotFilter) Reset() { *x = NotFilter{} - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10531,7 +10735,7 @@ func (x *NotFilter) String() string { func (*NotFilter) ProtoMessage() {} func (x *NotFilter) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[133] + mi := &file_common_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10544,7 +10748,7 @@ func (x *NotFilter) ProtoReflect() protoreflect.Message { // Deprecated: Use NotFilter.ProtoReflect.Descriptor instead. func (*NotFilter) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{133} + return file_common_proto_rawDescGZIP(), []int{134} } func (x *NotFilter) GetFilter() *QueryFilter { @@ -10566,7 +10770,7 @@ type FieldRef struct { func (x *FieldRef) Reset() { *x = FieldRef{} - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10578,7 +10782,7 @@ func (x *FieldRef) String() string { func (*FieldRef) ProtoMessage() {} func (x *FieldRef) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[134] + mi := &file_common_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10591,7 +10795,7 @@ func (x *FieldRef) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldRef.ProtoReflect.Descriptor instead. func (*FieldRef) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{134} + return file_common_proto_rawDescGZIP(), []int{135} } func (x *FieldRef) GetMetadata() string { @@ -10619,7 +10823,7 @@ type FieldCondition struct { func (x *FieldCondition) Reset() { *x = FieldCondition{} - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10631,7 +10835,7 @@ func (x *FieldCondition) String() string { func (*FieldCondition) ProtoMessage() {} func (x *FieldCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[135] + mi := &file_common_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10644,7 +10848,7 @@ func (x *FieldCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldCondition.ProtoReflect.Descriptor instead. func (*FieldCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{135} + return file_common_proto_rawDescGZIP(), []int{136} } func (x *FieldCondition) GetField() *FieldRef { @@ -10753,7 +10957,7 @@ type StringCondition struct { func (x *StringCondition) Reset() { *x = StringCondition{} - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10765,7 +10969,7 @@ func (x *StringCondition) String() string { func (*StringCondition) ProtoMessage() {} func (x *StringCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[136] + mi := &file_common_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10778,7 +10982,7 @@ func (x *StringCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use StringCondition.ProtoReflect.Descriptor instead. func (*StringCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{136} + return file_common_proto_rawDescGZIP(), []int{137} } func (x *StringCondition) GetValue() isStringCondition_Value { @@ -10836,7 +11040,7 @@ type IntCondition struct { func (x *IntCondition) Reset() { *x = IntCondition{} - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10848,7 +11052,7 @@ func (x *IntCondition) String() string { func (*IntCondition) ProtoMessage() {} func (x *IntCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[137] + mi := &file_common_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10861,7 +11065,7 @@ func (x *IntCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use IntCondition.ProtoReflect.Descriptor instead. func (*IntCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{137} + return file_common_proto_rawDescGZIP(), []int{138} } func (x *IntCondition) GetMin() int64 { @@ -10920,7 +11124,7 @@ type UintCondition struct { func (x *UintCondition) Reset() { *x = UintCondition{} - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10932,7 +11136,7 @@ func (x *UintCondition) String() string { func (*UintCondition) ProtoMessage() {} func (x *UintCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[138] + mi := &file_common_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10945,7 +11149,7 @@ func (x *UintCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use UintCondition.ProtoReflect.Descriptor instead. func (*UintCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{138} + return file_common_proto_rawDescGZIP(), []int{139} } func (x *UintCondition) GetMin() uint64 { @@ -11003,7 +11207,7 @@ type BoolCondition struct { func (x *BoolCondition) Reset() { *x = BoolCondition{} - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11015,7 +11219,7 @@ func (x *BoolCondition) String() string { func (*BoolCondition) ProtoMessage() {} func (x *BoolCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[139] + mi := &file_common_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11028,7 +11232,7 @@ func (x *BoolCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use BoolCondition.ProtoReflect.Descriptor instead. func (*BoolCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{139} + return file_common_proto_rawDescGZIP(), []int{140} } func (x *BoolCondition) GetValue() isBoolCondition_Value { @@ -11081,7 +11285,7 @@ type ExistsCondition struct { func (x *ExistsCondition) Reset() { *x = ExistsCondition{} - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11093,7 +11297,7 @@ func (x *ExistsCondition) String() string { func (*ExistsCondition) ProtoMessage() {} func (x *ExistsCondition) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[140] + mi := &file_common_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11106,7 +11310,7 @@ func (x *ExistsCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use ExistsCondition.ProtoReflect.Descriptor instead. func (*ExistsCondition) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{140} + return file_common_proto_rawDescGZIP(), []int{141} } func (x *ExistsCondition) GetIncludeNull() bool { @@ -11132,7 +11336,7 @@ type AddressMatch struct { func (x *AddressMatch) Reset() { *x = AddressMatch{} - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11144,7 +11348,7 @@ func (x *AddressMatch) String() string { func (*AddressMatch) ProtoMessage() {} func (x *AddressMatch) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[141] + mi := &file_common_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11157,7 +11361,7 @@ func (x *AddressMatch) ProtoReflect() protoreflect.Message { // Deprecated: Use AddressMatch.ProtoReflect.Descriptor instead. func (*AddressMatch) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{141} + return file_common_proto_rawDescGZIP(), []int{142} } func (x *AddressMatch) GetMatch() isAddressMatch_Match { @@ -11253,7 +11457,7 @@ type PreparedQuery struct { func (x *PreparedQuery) Reset() { *x = PreparedQuery{} - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11265,7 +11469,7 @@ func (x *PreparedQuery) String() string { func (*PreparedQuery) ProtoMessage() {} func (x *PreparedQuery) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[142] + mi := &file_common_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11278,7 +11482,7 @@ func (x *PreparedQuery) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQuery.ProtoReflect.Descriptor instead. func (*PreparedQuery) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{142} + return file_common_proto_rawDescGZIP(), []int{143} } func (x *PreparedQuery) GetName() string { @@ -11314,7 +11518,7 @@ type AggregatedVolume struct { func (x *AggregatedVolume) Reset() { *x = AggregatedVolume{} - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11326,7 +11530,7 @@ func (x *AggregatedVolume) String() string { func (*AggregatedVolume) ProtoMessage() {} func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[143] + mi := &file_common_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11339,7 +11543,7 @@ func (x *AggregatedVolume) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregatedVolume.ProtoReflect.Descriptor instead. func (*AggregatedVolume) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{143} + return file_common_proto_rawDescGZIP(), []int{144} } func (x *AggregatedVolume) GetAsset() string { @@ -11374,7 +11578,7 @@ type AggregateResult struct { func (x *AggregateResult) Reset() { *x = AggregateResult{} - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11386,7 +11590,7 @@ func (x *AggregateResult) String() string { func (*AggregateResult) ProtoMessage() {} func (x *AggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[144] + mi := &file_common_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11399,7 +11603,7 @@ func (x *AggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use AggregateResult.ProtoReflect.Descriptor instead. func (*AggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{144} + return file_common_proto_rawDescGZIP(), []int{145} } func (x *AggregateResult) GetVolumes() []*AggregatedVolume { @@ -11427,7 +11631,7 @@ type GroupedAggregateResult struct { func (x *GroupedAggregateResult) Reset() { *x = GroupedAggregateResult{} - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11439,7 +11643,7 @@ func (x *GroupedAggregateResult) String() string { func (*GroupedAggregateResult) ProtoMessage() {} func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[145] + mi := &file_common_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11452,7 +11656,7 @@ func (x *GroupedAggregateResult) ProtoReflect() protoreflect.Message { // Deprecated: Use GroupedAggregateResult.ProtoReflect.Descriptor instead. func (*GroupedAggregateResult) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{145} + return file_common_proto_rawDescGZIP(), []int{146} } func (x *GroupedAggregateResult) GetPrefix() string { @@ -11484,7 +11688,7 @@ type PreparedQueryCursor struct { func (x *PreparedQueryCursor) Reset() { *x = PreparedQueryCursor{} - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11496,7 +11700,7 @@ func (x *PreparedQueryCursor) String() string { func (*PreparedQueryCursor) ProtoMessage() {} func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[146] + mi := &file_common_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11509,7 +11713,7 @@ func (x *PreparedQueryCursor) ProtoReflect() protoreflect.Message { // Deprecated: Use PreparedQueryCursor.ProtoReflect.Descriptor instead. func (*PreparedQueryCursor) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{146} + return file_common_proto_rawDescGZIP(), []int{147} } func (x *PreparedQueryCursor) GetPageSize() uint32 { @@ -11572,7 +11776,7 @@ type LedgerStats struct { func (x *LedgerStats) Reset() { *x = LedgerStats{} - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11584,7 +11788,7 @@ func (x *LedgerStats) String() string { func (*LedgerStats) ProtoMessage() {} func (x *LedgerStats) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[147] + mi := &file_common_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11597,7 +11801,7 @@ func (x *LedgerStats) ProtoReflect() protoreflect.Message { // Deprecated: Use LedgerStats.ProtoReflect.Descriptor instead. func (*LedgerStats) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{147} + return file_common_proto_rawDescGZIP(), []int{148} } func (x *LedgerStats) GetTransactionCount() uint64 { @@ -11678,7 +11882,7 @@ type PersistedConfig struct { func (x *PersistedConfig) Reset() { *x = PersistedConfig{} - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11690,7 +11894,7 @@ func (x *PersistedConfig) String() string { func (*PersistedConfig) ProtoMessage() {} func (x *PersistedConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[148] + mi := &file_common_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11703,7 +11907,7 @@ func (x *PersistedConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PersistedConfig.ProtoReflect.Descriptor instead. func (*PersistedConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{148} + return file_common_proto_rawDescGZIP(), []int{149} } func (x *PersistedConfig) GetNodeId() uint64 { @@ -11761,7 +11965,7 @@ type CallerIdentity struct { func (x *CallerIdentity) Reset() { *x = CallerIdentity{} - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11773,7 +11977,7 @@ func (x *CallerIdentity) String() string { func (*CallerIdentity) ProtoMessage() {} func (x *CallerIdentity) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[149] + mi := &file_common_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11786,7 +11990,7 @@ func (x *CallerIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerIdentity.ProtoReflect.Descriptor instead. func (*CallerIdentity) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{149} + return file_common_proto_rawDescGZIP(), []int{150} } func (x *CallerIdentity) GetSubject() string { @@ -11873,7 +12077,7 @@ type CallerSnapshot struct { func (x *CallerSnapshot) Reset() { *x = CallerSnapshot{} - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11885,7 +12089,7 @@ func (x *CallerSnapshot) String() string { func (*CallerSnapshot) ProtoMessage() {} func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[150] + mi := &file_common_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11898,7 +12102,7 @@ func (x *CallerSnapshot) ProtoReflect() protoreflect.Message { // Deprecated: Use CallerSnapshot.ProtoReflect.Descriptor instead. func (*CallerSnapshot) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{150} + return file_common_proto_rawDescGZIP(), []int{151} } func (x *CallerSnapshot) GetIdentity() *CallerIdentity { @@ -11936,7 +12140,7 @@ type S3StorageConfig struct { func (x *S3StorageConfig) Reset() { *x = S3StorageConfig{} - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11948,7 +12152,7 @@ func (x *S3StorageConfig) String() string { func (*S3StorageConfig) ProtoMessage() {} func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[151] + mi := &file_common_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11961,7 +12165,7 @@ func (x *S3StorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use S3StorageConfig.ProtoReflect.Descriptor instead. func (*S3StorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{151} + return file_common_proto_rawDescGZIP(), []int{152} } func (x *S3StorageConfig) GetBucket() string { @@ -12012,7 +12216,7 @@ type AzureStorageConfig struct { func (x *AzureStorageConfig) Reset() { *x = AzureStorageConfig{} - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12024,7 +12228,7 @@ func (x *AzureStorageConfig) String() string { func (*AzureStorageConfig) ProtoMessage() {} func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[152] + mi := &file_common_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12037,7 +12241,7 @@ func (x *AzureStorageConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use AzureStorageConfig.ProtoReflect.Descriptor instead. func (*AzureStorageConfig) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{152} + return file_common_proto_rawDescGZIP(), []int{153} } func (x *AzureStorageConfig) GetAccountName() string { @@ -12084,7 +12288,7 @@ type BackupStorage struct { func (x *BackupStorage) Reset() { *x = BackupStorage{} - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[154] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12096,7 +12300,7 @@ func (x *BackupStorage) String() string { func (*BackupStorage) ProtoMessage() {} func (x *BackupStorage) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[153] + mi := &file_common_proto_msgTypes[154] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12109,7 +12313,7 @@ func (x *BackupStorage) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupStorage.ProtoReflect.Descriptor instead. func (*BackupStorage) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{153} + return file_common_proto_rawDescGZIP(), []int{154} } func (x *BackupStorage) GetProvider() isBackupStorage_Provider { @@ -12172,7 +12376,7 @@ type ReadOptions struct { func (x *ReadOptions) Reset() { *x = ReadOptions{} - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[155] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12184,7 +12388,7 @@ func (x *ReadOptions) String() string { func (*ReadOptions) ProtoMessage() {} func (x *ReadOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[154] + mi := &file_common_proto_msgTypes[155] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12197,7 +12401,7 @@ func (x *ReadOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ReadOptions.ProtoReflect.Descriptor instead. func (*ReadOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{154} + return file_common_proto_rawDescGZIP(), []int{155} } func (x *ReadOptions) GetCheckpointId() uint64 { @@ -12249,7 +12453,7 @@ type ListOptions struct { func (x *ListOptions) Reset() { *x = ListOptions{} - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[156] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12261,7 +12465,7 @@ func (x *ListOptions) String() string { func (*ListOptions) ProtoMessage() {} func (x *ListOptions) ProtoReflect() protoreflect.Message { - mi := &file_common_proto_msgTypes[155] + mi := &file_common_proto_msgTypes[156] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12274,7 +12478,7 @@ func (x *ListOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOptions.ProtoReflect.Descriptor instead. func (*ListOptions) Descriptor() ([]byte, []int) { - return file_common_proto_rawDescGZIP(), []int{155} + return file_common_proto_rawDescGZIP(), []int{156} } func (x *ListOptions) GetRead() *ReadOptions { @@ -12674,14 +12878,14 @@ const file_common_proto_rawDesc = "" + "\x0eApplyLedgerLog\x12\x1f\n" + "\vledger_name\x18\x01 \x01(\tR\n" + "ledgerName\x12#\n" + - "\x03log\x18\x02 \x01(\v2\x11.common.LedgerLogR\x03log\"\xe6\x01\n" + + "\x03log\x18\x02 \x01(\v2\x11.common.LedgerLogR\x03log\"\xb3\x02\n" + "\tLedgerLog\x12,\n" + "\x04data\x18\x01 \x01(\v2\x18.common.LedgerLogPayloadR\x04data\x12%\n" + "\x04date\x18\x02 \x01(\v2\x11.common.TimestampR\x04date\x12\x0e\n" + "\x02id\x18\x03 \x01(\x06R\x02id\x12<\n" + - "\x0epurged_volumes\x18\x04 \x03(\v2\x15.common.TouchedVolumeR\rpurgedVolumes\x126\n" + - "\vnew_volumes\x18\x05 \x03(\v2\x15.common.TouchedVolumeR\n" + - "newVolumes\"?\n" + + "\x0epurged_volumes\x18\x04 \x03(\v2\x15.common.TouchedVolumeR\rpurgedVolumes\x12?\n" + + "\x10new_kept_volumes\x18\x05 \x03(\v2\x15.common.TouchedVolumeR\x0enewKeptVolumes\x12B\n" + + "\x11ephemeral_volumes\x18\x06 \x03(\v2\x15.common.TouchedVolumeR\x10ephemeralVolumes\"?\n" + "\rTouchedVolume\x12\x18\n" + "\aaccount\x18\x01 \x01(\tR\aaccount\x12\x14\n" + "\x05asset\x18\x02 \x01(\tR\x05asset\"\xc4\a\n" + @@ -12968,7 +13172,7 @@ const file_common_proto_rawDesc = "" + "\x15RemovedAccountTypeLog\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\"k\n" + " UpdatedDefaultEnforcementModeLog\x12G\n" + - "\x10enforcement_mode\x18\x01 \x01(\x0e2\x1c.common.ChartEnforcementModeR\x0fenforcementMode\"\xa4\x05\n" + + "\x10enforcement_mode\x18\x01 \x01(\x0e2\x1c.common.ChartEnforcementModeR\x0fenforcementMode\"\xd4\x05\n" + "\vQueryFilter\x12.\n" + "\x05field\x18\x01 \x01(\v2\x16.common.FieldConditionH\x00R\x05field\x120\n" + "\aaddress\x18\x02 \x01(\v2\x14.common.AddressMatchH\x00R\aaddress\x12%\n" + @@ -12982,12 +13186,19 @@ const file_common_proto_rawDesc = "" + "\x10log_builtin_uint\x18\n" + " \x01(\v2\x1f.common.LogBuiltinUintConditionH\x00R\x0elogBuiltinUint\x12N\n" + "\x11account_has_asset\x18\v \x01(\v2 .common.AccountHasAssetConditionH\x00R\x0faccountHasAsset\x127\n" + - "\breverted\x18\f \x01(\v2\x19.common.RevertedConditionH\x00R\brevertedB\b\n" + + "\breverted\x18\f \x01(\v2\x19.common.RevertedConditionH\x00R\breverted\x12.\n" + + "\x05audit\x18\r \x01(\v2\x16.common.AuditConditionH\x00R\x05auditB\b\n" + "\x06filter\"A\n" + "\x12ReferenceCondition\x12+\n" + "\x04cond\x18\x01 \x01(\v2\x17.common.StringConditionR\x04cond\")\n" + "\x11RevertedCondition\x12\x14\n" + - "\x05value\x18\x01 \x01(\bR\x05value\">\n" + + "\x05value\x18\x01 \x01(\bR\x05value\"\xb9\x01\n" + + "\x0eAuditCondition\x12(\n" + + "\x05field\x18\x01 \x01(\x0e2\x12.common.AuditFieldR\x05field\x12:\n" + + "\vstring_cond\x18\x02 \x01(\v2\x17.common.StringConditionH\x00R\n" + + "stringCond\x124\n" + + "\tuint_cond\x18\x03 \x01(\v2\x15.common.UintConditionH\x00R\buintCondB\v\n" + + "\tcondition\">\n" + "\x0fLedgerCondition\x12+\n" + "\x04cond\x18\x01 \x01(\v2\x17.common.StringConditionR\x04cond\";\n" + "\x0eLogIdCondition\x12)\n" + @@ -13263,7 +13474,18 @@ const file_common_proto_rawDesc = "" + "\x16AccountTypePersistence\x12\x17\n" + "\x13ACCOUNT_TYPE_NORMAL\x10\x00\x12\x1a\n" + "\x16ACCOUNT_TYPE_EPHEMERAL\x10\x01\x12\x1a\n" + - "\x16ACCOUNT_TYPE_TRANSIENT\x10\x02*Z\n" + + "\x16ACCOUNT_TYPE_TRANSIENT\x10\x02*\x86\x02\n" + + "\n" + + "AuditField\x12\x1b\n" + + "\x17AUDIT_FIELD_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14AUDIT_FIELD_SEQUENCE\x10\x01\x12\x1b\n" + + "\x17AUDIT_FIELD_PROPOSAL_ID\x10\x02\x12\x19\n" + + "\x15AUDIT_FIELD_TIMESTAMP\x10\x03\x12\x1c\n" + + "\x18AUDIT_FIELD_LOG_SEQUENCE\x10\x04\x12\x17\n" + + "\x13AUDIT_FIELD_OUTCOME\x10\x05\x12\x1e\n" + + "\x1aAUDIT_FIELD_CALLER_SUBJECT\x10\x06\x12\x16\n" + + "\x12AUDIT_FIELD_LEDGER\x10\a\x12\x1a\n" + + "\x16AUDIT_FIELD_ORDER_TYPE\x10\b*Z\n" + "\vAddressRole\x12\x14\n" + "\x10ADDRESS_ROLE_ANY\x10\x00\x12\x17\n" + "\x13ADDRESS_ROLE_SOURCE\x10\x01\x12\x1c\n" + @@ -13288,8 +13510,8 @@ func file_common_proto_rawDescGZIP() []byte { return file_common_proto_rawDescData } -var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 17) -var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 176) +var file_common_proto_enumTypes = make([]protoimpl.EnumInfo, 18) +var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 177) var file_common_proto_goTypes = []any{ (TargetType)(0), // 0: common.TargetType (MetadataType)(0), // 1: common.MetadataType @@ -13305,457 +13527,464 @@ var file_common_proto_goTypes = []any{ (ErrorReason)(0), // 11: common.ErrorReason (ChartEnforcementMode)(0), // 12: common.ChartEnforcementMode (AccountTypePersistence)(0), // 13: common.AccountTypePersistence - (AddressRole)(0), // 14: common.AddressRole - (QueryTarget)(0), // 15: common.QueryTarget - (QueryMode)(0), // 16: common.QueryMode - (*Timestamp)(nil), // 17: common.Timestamp - (*NullValue)(nil), // 18: common.NullValue - (*MetadataValue)(nil), // 19: common.MetadataValue - (*MetadataMap)(nil), // 20: common.MetadataMap - (*ParameterValue)(nil), // 21: common.ParameterValue - (*Uint256)(nil), // 22: common.Uint256 - (*Posting)(nil), // 23: common.Posting - (*Transaction)(nil), // 24: common.Transaction - (*Script)(nil), // 25: common.Script - (*Volumes)(nil), // 26: common.Volumes - (*VolumesWithBalance)(nil), // 27: common.VolumesWithBalance - (*VolumesByAssets)(nil), // 28: common.VolumesByAssets - (*PostCommitVolumes)(nil), // 29: common.PostCommitVolumes - (*Account)(nil), // 30: common.Account - (*TargetAccount)(nil), // 31: common.TargetAccount - (*Target)(nil), // 32: common.Target - (*MetadataFieldSchema)(nil), // 33: common.MetadataFieldSchema - (*MetadataSchema)(nil), // 34: common.MetadataSchema - (*SetMetadataFieldTypeCommand)(nil), // 35: common.SetMetadataFieldTypeCommand - (*MetadataIndexID)(nil), // 36: common.MetadataIndexID - (*IndexID)(nil), // 37: common.IndexID - (*Index)(nil), // 38: common.Index - (*Idempotency)(nil), // 39: common.Idempotency - (*IdempotencyEntry)(nil), // 40: common.IdempotencyEntry - (*Log)(nil), // 41: common.Log - (*LogPayload)(nil), // 42: common.LogPayload - (*PromotedLedgerLog)(nil), // 43: common.PromotedLedgerLog - (*RegisteredSigningKeyLog)(nil), // 44: common.RegisteredSigningKeyLog - (*RevokedSigningKeyLog)(nil), // 45: common.RevokedSigningKeyLog - (*SigningKey)(nil), // 46: common.SigningKey - (*SetSigningConfigLog)(nil), // 47: common.SetSigningConfigLog - (*AddedEventsSinkLog)(nil), // 48: common.AddedEventsSinkLog - (*RemovedEventsSinkLog)(nil), // 49: common.RemovedEventsSinkLog - (*SetMaintenanceModeLog)(nil), // 50: common.SetMaintenanceModeLog - (*BloomTypeConfig)(nil), // 51: common.BloomTypeConfig - (*ClusterConfig)(nil), // 52: common.ClusterConfig - (*PersistedClusterState)(nil), // 53: common.PersistedClusterState - (*SetChapterScheduleLog)(nil), // 54: common.SetChapterScheduleLog - (*DeletedChapterScheduleLog)(nil), // 55: common.DeletedChapterScheduleLog - (*CreatedPreparedQueryLog)(nil), // 56: common.CreatedPreparedQueryLog - (*UpdatedPreparedQueryLog)(nil), // 57: common.UpdatedPreparedQueryLog - (*DeletedPreparedQueryLog)(nil), // 58: common.DeletedPreparedQueryLog - (*SavedLedgerMetadataLog)(nil), // 59: common.SavedLedgerMetadataLog - (*DeletedLedgerMetadataLog)(nil), // 60: common.DeletedLedgerMetadataLog - (*NumscriptInfo)(nil), // 61: common.NumscriptInfo - (*SavedNumscriptLog)(nil), // 62: common.SavedNumscriptLog - (*DeletedNumscriptLog)(nil), // 63: common.DeletedNumscriptLog - (*TemplateUsage)(nil), // 64: common.TemplateUsage - (*SetQueryCheckpointScheduleLog)(nil), // 65: common.SetQueryCheckpointScheduleLog - (*DeletedQueryCheckpointScheduleLog)(nil), // 66: common.DeletedQueryCheckpointScheduleLog - (*CreatedQueryCheckpointLog)(nil), // 67: common.CreatedQueryCheckpointLog - (*DeletedQueryCheckpointLog)(nil), // 68: common.DeletedQueryCheckpointLog - (*SinkConfig)(nil), // 69: common.SinkConfig - (*SinkStatus)(nil), // 70: common.SinkStatus - (*SinkError)(nil), // 71: common.SinkError - (*NatsSinkConfig)(nil), // 72: common.NatsSinkConfig - (*ClickHouseSinkConfig)(nil), // 73: common.ClickHouseSinkConfig - (*KafkaSinkConfig)(nil), // 74: common.KafkaSinkConfig - (*HttpSinkConfig)(nil), // 75: common.HttpSinkConfig - (*DatabricksSinkConfig)(nil), // 76: common.DatabricksSinkConfig - (*DatabricksOAuthM2M)(nil), // 77: common.DatabricksOAuthM2M - (*CreatedLedgerLog)(nil), // 78: common.CreatedLedgerLog - (*DeletedLedgerLog)(nil), // 79: common.DeletedLedgerLog - (*ApplyLedgerLog)(nil), // 80: common.ApplyLedgerLog - (*LedgerLog)(nil), // 81: common.LedgerLog - (*TouchedVolume)(nil), // 82: common.TouchedVolume - (*LedgerLogPayload)(nil), // 83: common.LedgerLogPayload - (*CreatedIndexLog)(nil), // 84: common.CreatedIndexLog - (*DroppedIndexLog)(nil), // 85: common.DroppedIndexLog - (*FilledGapLog)(nil), // 86: common.FilledGapLog - (*CreatedTransaction)(nil), // 87: common.CreatedTransaction - (*RevertedTransaction)(nil), // 88: common.RevertedTransaction - (*SavedMetadata)(nil), // 89: common.SavedMetadata - (*DeletedMetadata)(nil), // 90: common.DeletedMetadata - (*SetMetadataFieldTypeLog)(nil), // 91: common.SetMetadataFieldTypeLog - (*RemovedMetadataFieldTypeLog)(nil), // 92: common.RemovedMetadataFieldTypeLog - (*Chapter)(nil), // 93: common.Chapter - (*ClosedChapterLog)(nil), // 94: common.ClosedChapterLog - (*SealedChapterLog)(nil), // 95: common.SealedChapterLog - (*ArchivedChapterLog)(nil), // 96: common.ArchivedChapterLog - (*ConfirmedArchiveChapterLog)(nil), // 97: common.ConfirmedArchiveChapterLog - (*MirrorSourceConfig)(nil), // 98: common.MirrorSourceConfig - (*MirrorRewriteRule)(nil), // 99: common.MirrorRewriteRule - (*CreatedTransactionRule)(nil), // 100: common.CreatedTransactionRule - (*RevertedTransactionRule)(nil), // 101: common.RevertedTransactionRule - (*SavedMetadataRule)(nil), // 102: common.SavedMetadataRule - (*DeletedMetadataRule)(nil), // 103: common.DeletedMetadataRule - (*AnyVariantRule)(nil), // 104: common.AnyVariantRule - (*CreatedTransactionAction)(nil), // 105: common.CreatedTransactionAction - (*RevertedTransactionAction)(nil), // 106: common.RevertedTransactionAction - (*SavedMetadataAction)(nil), // 107: common.SavedMetadataAction - (*DeletedMetadataAction)(nil), // 108: common.DeletedMetadataAction - (*AnyVariantAction)(nil), // 109: common.AnyVariantAction - (*RewriteAddressAction)(nil), // 110: common.RewriteAddressAction - (*SetMetadataAction)(nil), // 111: common.SetMetadataAction - (*DeleteMetadataAction)(nil), // 112: common.DeleteMetadataAction - (*SetAccountMetadataAction)(nil), // 113: common.SetAccountMetadataAction - (*DeleteAccountMetadataAction)(nil), // 114: common.DeleteAccountMetadataAction - (*SetAccountMetadataFromAddressAction)(nil), // 115: common.SetAccountMetadataFromAddressAction - (*SetAccountMetadataFromAddressReplacement)(nil), // 116: common.SetAccountMetadataFromAddressReplacement - (*DropAction)(nil), // 117: common.DropAction - (*HttpMirrorSourceConfig)(nil), // 118: common.HttpMirrorSourceConfig - (*OAuth2ClientCredentials)(nil), // 119: common.OAuth2ClientCredentials - (*PostgresMirrorSourceConfig)(nil), // 120: common.PostgresMirrorSourceConfig - (*PostgresAwsIamAuth)(nil), // 121: common.PostgresAwsIamAuth - (*MirrorSyncError)(nil), // 122: common.MirrorSyncError - (*MirrorSyncProgress)(nil), // 123: common.MirrorSyncProgress - (*LedgerInfo)(nil), // 124: common.LedgerInfo - (*SaveMetadataCommand)(nil), // 125: common.SaveMetadataCommand - (*DeleteMetadataCommand)(nil), // 126: common.DeleteMetadataCommand - (*TransactionState)(nil), // 127: common.TransactionState - (*IdempotencyKeyValue)(nil), // 128: common.IdempotencyKeyValue - (*IdempotencyFailure)(nil), // 129: common.IdempotencyFailure - (*TransactionReferenceValue)(nil), // 130: common.TransactionReferenceValue - (*NumscriptVersionValue)(nil), // 131: common.NumscriptVersionValue - (*SegmentType)(nil), // 132: common.SegmentType - (*UUIDConstraint)(nil), // 133: common.UUIDConstraint - (*Uint64Constraint)(nil), // 134: common.Uint64Constraint - (*BytesConstraint)(nil), // 135: common.BytesConstraint - (*AccountType)(nil), // 136: common.AccountType - (*AddedAccountTypeLog)(nil), // 137: common.AddedAccountTypeLog - (*RemovedAccountTypeLog)(nil), // 138: common.RemovedAccountTypeLog - (*UpdatedDefaultEnforcementModeLog)(nil), // 139: common.UpdatedDefaultEnforcementModeLog - (*QueryFilter)(nil), // 140: common.QueryFilter - (*ReferenceCondition)(nil), // 141: common.ReferenceCondition - (*RevertedCondition)(nil), // 142: common.RevertedCondition - (*LedgerCondition)(nil), // 143: common.LedgerCondition - (*LogIdCondition)(nil), // 144: common.LogIdCondition - (*BuiltinUintCondition)(nil), // 145: common.BuiltinUintCondition - (*LogBuiltinUintCondition)(nil), // 146: common.LogBuiltinUintCondition - (*AccountHasAssetCondition)(nil), // 147: common.AccountHasAssetCondition - (*AndFilter)(nil), // 148: common.AndFilter - (*OrFilter)(nil), // 149: common.OrFilter - (*NotFilter)(nil), // 150: common.NotFilter - (*FieldRef)(nil), // 151: common.FieldRef - (*FieldCondition)(nil), // 152: common.FieldCondition - (*StringCondition)(nil), // 153: common.StringCondition - (*IntCondition)(nil), // 154: common.IntCondition - (*UintCondition)(nil), // 155: common.UintCondition - (*BoolCondition)(nil), // 156: common.BoolCondition - (*ExistsCondition)(nil), // 157: common.ExistsCondition - (*AddressMatch)(nil), // 158: common.AddressMatch - (*PreparedQuery)(nil), // 159: common.PreparedQuery - (*AggregatedVolume)(nil), // 160: common.AggregatedVolume - (*AggregateResult)(nil), // 161: common.AggregateResult - (*GroupedAggregateResult)(nil), // 162: common.GroupedAggregateResult - (*PreparedQueryCursor)(nil), // 163: common.PreparedQueryCursor - (*LedgerStats)(nil), // 164: common.LedgerStats - (*PersistedConfig)(nil), // 165: common.PersistedConfig - (*CallerIdentity)(nil), // 166: common.CallerIdentity - (*CallerSnapshot)(nil), // 167: common.CallerSnapshot - (*S3StorageConfig)(nil), // 168: common.S3StorageConfig - (*AzureStorageConfig)(nil), // 169: common.AzureStorageConfig - (*BackupStorage)(nil), // 170: common.BackupStorage - (*ReadOptions)(nil), // 171: common.ReadOptions - (*ListOptions)(nil), // 172: common.ListOptions - nil, // 173: common.MetadataMap.ValuesEntry - nil, // 174: common.Transaction.MetadataEntry - nil, // 175: common.Script.VarsEntry - nil, // 176: common.VolumesByAssets.VolumesEntry - nil, // 177: common.PostCommitVolumes.VolumesByAccountEntry - nil, // 178: common.Account.MetadataEntry - nil, // 179: common.Account.VolumesEntry - nil, // 180: common.MetadataSchema.AccountFieldsEntry - nil, // 181: common.MetadataSchema.TransactionFieldsEntry - nil, // 182: common.MetadataSchema.LedgerFieldsEntry - nil, // 183: common.SavedLedgerMetadataLog.MetadataEntry - nil, // 184: common.CreatedLedgerLog.AccountTypesEntry - nil, // 185: common.CreatedTransaction.AccountMetadataEntry - nil, // 186: common.SavedMetadata.MetadataEntry - nil, // 187: common.LedgerInfo.AccountTypesEntry - nil, // 188: common.LedgerInfo.MetadataEntry - nil, // 189: common.SaveMetadataCommand.MetadataEntry - nil, // 190: common.TransactionState.MetadataEntry - nil, // 191: common.IdempotencyFailure.MetadataEntry - nil, // 192: common.AccountType.SegmentTypesEntry - (*signaturepb.SignedLog)(nil), // 193: signature.SignedLog + (AuditField)(0), // 14: common.AuditField + (AddressRole)(0), // 15: common.AddressRole + (QueryTarget)(0), // 16: common.QueryTarget + (QueryMode)(0), // 17: common.QueryMode + (*Timestamp)(nil), // 18: common.Timestamp + (*NullValue)(nil), // 19: common.NullValue + (*MetadataValue)(nil), // 20: common.MetadataValue + (*MetadataMap)(nil), // 21: common.MetadataMap + (*ParameterValue)(nil), // 22: common.ParameterValue + (*Uint256)(nil), // 23: common.Uint256 + (*Posting)(nil), // 24: common.Posting + (*Transaction)(nil), // 25: common.Transaction + (*Script)(nil), // 26: common.Script + (*Volumes)(nil), // 27: common.Volumes + (*VolumesWithBalance)(nil), // 28: common.VolumesWithBalance + (*VolumesByAssets)(nil), // 29: common.VolumesByAssets + (*PostCommitVolumes)(nil), // 30: common.PostCommitVolumes + (*Account)(nil), // 31: common.Account + (*TargetAccount)(nil), // 32: common.TargetAccount + (*Target)(nil), // 33: common.Target + (*MetadataFieldSchema)(nil), // 34: common.MetadataFieldSchema + (*MetadataSchema)(nil), // 35: common.MetadataSchema + (*SetMetadataFieldTypeCommand)(nil), // 36: common.SetMetadataFieldTypeCommand + (*MetadataIndexID)(nil), // 37: common.MetadataIndexID + (*IndexID)(nil), // 38: common.IndexID + (*Index)(nil), // 39: common.Index + (*Idempotency)(nil), // 40: common.Idempotency + (*IdempotencyEntry)(nil), // 41: common.IdempotencyEntry + (*Log)(nil), // 42: common.Log + (*LogPayload)(nil), // 43: common.LogPayload + (*PromotedLedgerLog)(nil), // 44: common.PromotedLedgerLog + (*RegisteredSigningKeyLog)(nil), // 45: common.RegisteredSigningKeyLog + (*RevokedSigningKeyLog)(nil), // 46: common.RevokedSigningKeyLog + (*SigningKey)(nil), // 47: common.SigningKey + (*SetSigningConfigLog)(nil), // 48: common.SetSigningConfigLog + (*AddedEventsSinkLog)(nil), // 49: common.AddedEventsSinkLog + (*RemovedEventsSinkLog)(nil), // 50: common.RemovedEventsSinkLog + (*SetMaintenanceModeLog)(nil), // 51: common.SetMaintenanceModeLog + (*BloomTypeConfig)(nil), // 52: common.BloomTypeConfig + (*ClusterConfig)(nil), // 53: common.ClusterConfig + (*PersistedClusterState)(nil), // 54: common.PersistedClusterState + (*SetChapterScheduleLog)(nil), // 55: common.SetChapterScheduleLog + (*DeletedChapterScheduleLog)(nil), // 56: common.DeletedChapterScheduleLog + (*CreatedPreparedQueryLog)(nil), // 57: common.CreatedPreparedQueryLog + (*UpdatedPreparedQueryLog)(nil), // 58: common.UpdatedPreparedQueryLog + (*DeletedPreparedQueryLog)(nil), // 59: common.DeletedPreparedQueryLog + (*SavedLedgerMetadataLog)(nil), // 60: common.SavedLedgerMetadataLog + (*DeletedLedgerMetadataLog)(nil), // 61: common.DeletedLedgerMetadataLog + (*NumscriptInfo)(nil), // 62: common.NumscriptInfo + (*SavedNumscriptLog)(nil), // 63: common.SavedNumscriptLog + (*DeletedNumscriptLog)(nil), // 64: common.DeletedNumscriptLog + (*TemplateUsage)(nil), // 65: common.TemplateUsage + (*SetQueryCheckpointScheduleLog)(nil), // 66: common.SetQueryCheckpointScheduleLog + (*DeletedQueryCheckpointScheduleLog)(nil), // 67: common.DeletedQueryCheckpointScheduleLog + (*CreatedQueryCheckpointLog)(nil), // 68: common.CreatedQueryCheckpointLog + (*DeletedQueryCheckpointLog)(nil), // 69: common.DeletedQueryCheckpointLog + (*SinkConfig)(nil), // 70: common.SinkConfig + (*SinkStatus)(nil), // 71: common.SinkStatus + (*SinkError)(nil), // 72: common.SinkError + (*NatsSinkConfig)(nil), // 73: common.NatsSinkConfig + (*ClickHouseSinkConfig)(nil), // 74: common.ClickHouseSinkConfig + (*KafkaSinkConfig)(nil), // 75: common.KafkaSinkConfig + (*HttpSinkConfig)(nil), // 76: common.HttpSinkConfig + (*DatabricksSinkConfig)(nil), // 77: common.DatabricksSinkConfig + (*DatabricksOAuthM2M)(nil), // 78: common.DatabricksOAuthM2M + (*CreatedLedgerLog)(nil), // 79: common.CreatedLedgerLog + (*DeletedLedgerLog)(nil), // 80: common.DeletedLedgerLog + (*ApplyLedgerLog)(nil), // 81: common.ApplyLedgerLog + (*LedgerLog)(nil), // 82: common.LedgerLog + (*TouchedVolume)(nil), // 83: common.TouchedVolume + (*LedgerLogPayload)(nil), // 84: common.LedgerLogPayload + (*CreatedIndexLog)(nil), // 85: common.CreatedIndexLog + (*DroppedIndexLog)(nil), // 86: common.DroppedIndexLog + (*FilledGapLog)(nil), // 87: common.FilledGapLog + (*CreatedTransaction)(nil), // 88: common.CreatedTransaction + (*RevertedTransaction)(nil), // 89: common.RevertedTransaction + (*SavedMetadata)(nil), // 90: common.SavedMetadata + (*DeletedMetadata)(nil), // 91: common.DeletedMetadata + (*SetMetadataFieldTypeLog)(nil), // 92: common.SetMetadataFieldTypeLog + (*RemovedMetadataFieldTypeLog)(nil), // 93: common.RemovedMetadataFieldTypeLog + (*Chapter)(nil), // 94: common.Chapter + (*ClosedChapterLog)(nil), // 95: common.ClosedChapterLog + (*SealedChapterLog)(nil), // 96: common.SealedChapterLog + (*ArchivedChapterLog)(nil), // 97: common.ArchivedChapterLog + (*ConfirmedArchiveChapterLog)(nil), // 98: common.ConfirmedArchiveChapterLog + (*MirrorSourceConfig)(nil), // 99: common.MirrorSourceConfig + (*MirrorRewriteRule)(nil), // 100: common.MirrorRewriteRule + (*CreatedTransactionRule)(nil), // 101: common.CreatedTransactionRule + (*RevertedTransactionRule)(nil), // 102: common.RevertedTransactionRule + (*SavedMetadataRule)(nil), // 103: common.SavedMetadataRule + (*DeletedMetadataRule)(nil), // 104: common.DeletedMetadataRule + (*AnyVariantRule)(nil), // 105: common.AnyVariantRule + (*CreatedTransactionAction)(nil), // 106: common.CreatedTransactionAction + (*RevertedTransactionAction)(nil), // 107: common.RevertedTransactionAction + (*SavedMetadataAction)(nil), // 108: common.SavedMetadataAction + (*DeletedMetadataAction)(nil), // 109: common.DeletedMetadataAction + (*AnyVariantAction)(nil), // 110: common.AnyVariantAction + (*RewriteAddressAction)(nil), // 111: common.RewriteAddressAction + (*SetMetadataAction)(nil), // 112: common.SetMetadataAction + (*DeleteMetadataAction)(nil), // 113: common.DeleteMetadataAction + (*SetAccountMetadataAction)(nil), // 114: common.SetAccountMetadataAction + (*DeleteAccountMetadataAction)(nil), // 115: common.DeleteAccountMetadataAction + (*SetAccountMetadataFromAddressAction)(nil), // 116: common.SetAccountMetadataFromAddressAction + (*SetAccountMetadataFromAddressReplacement)(nil), // 117: common.SetAccountMetadataFromAddressReplacement + (*DropAction)(nil), // 118: common.DropAction + (*HttpMirrorSourceConfig)(nil), // 119: common.HttpMirrorSourceConfig + (*OAuth2ClientCredentials)(nil), // 120: common.OAuth2ClientCredentials + (*PostgresMirrorSourceConfig)(nil), // 121: common.PostgresMirrorSourceConfig + (*PostgresAwsIamAuth)(nil), // 122: common.PostgresAwsIamAuth + (*MirrorSyncError)(nil), // 123: common.MirrorSyncError + (*MirrorSyncProgress)(nil), // 124: common.MirrorSyncProgress + (*LedgerInfo)(nil), // 125: common.LedgerInfo + (*SaveMetadataCommand)(nil), // 126: common.SaveMetadataCommand + (*DeleteMetadataCommand)(nil), // 127: common.DeleteMetadataCommand + (*TransactionState)(nil), // 128: common.TransactionState + (*IdempotencyKeyValue)(nil), // 129: common.IdempotencyKeyValue + (*IdempotencyFailure)(nil), // 130: common.IdempotencyFailure + (*TransactionReferenceValue)(nil), // 131: common.TransactionReferenceValue + (*NumscriptVersionValue)(nil), // 132: common.NumscriptVersionValue + (*SegmentType)(nil), // 133: common.SegmentType + (*UUIDConstraint)(nil), // 134: common.UUIDConstraint + (*Uint64Constraint)(nil), // 135: common.Uint64Constraint + (*BytesConstraint)(nil), // 136: common.BytesConstraint + (*AccountType)(nil), // 137: common.AccountType + (*AddedAccountTypeLog)(nil), // 138: common.AddedAccountTypeLog + (*RemovedAccountTypeLog)(nil), // 139: common.RemovedAccountTypeLog + (*UpdatedDefaultEnforcementModeLog)(nil), // 140: common.UpdatedDefaultEnforcementModeLog + (*QueryFilter)(nil), // 141: common.QueryFilter + (*ReferenceCondition)(nil), // 142: common.ReferenceCondition + (*RevertedCondition)(nil), // 143: common.RevertedCondition + (*AuditCondition)(nil), // 144: common.AuditCondition + (*LedgerCondition)(nil), // 145: common.LedgerCondition + (*LogIdCondition)(nil), // 146: common.LogIdCondition + (*BuiltinUintCondition)(nil), // 147: common.BuiltinUintCondition + (*LogBuiltinUintCondition)(nil), // 148: common.LogBuiltinUintCondition + (*AccountHasAssetCondition)(nil), // 149: common.AccountHasAssetCondition + (*AndFilter)(nil), // 150: common.AndFilter + (*OrFilter)(nil), // 151: common.OrFilter + (*NotFilter)(nil), // 152: common.NotFilter + (*FieldRef)(nil), // 153: common.FieldRef + (*FieldCondition)(nil), // 154: common.FieldCondition + (*StringCondition)(nil), // 155: common.StringCondition + (*IntCondition)(nil), // 156: common.IntCondition + (*UintCondition)(nil), // 157: common.UintCondition + (*BoolCondition)(nil), // 158: common.BoolCondition + (*ExistsCondition)(nil), // 159: common.ExistsCondition + (*AddressMatch)(nil), // 160: common.AddressMatch + (*PreparedQuery)(nil), // 161: common.PreparedQuery + (*AggregatedVolume)(nil), // 162: common.AggregatedVolume + (*AggregateResult)(nil), // 163: common.AggregateResult + (*GroupedAggregateResult)(nil), // 164: common.GroupedAggregateResult + (*PreparedQueryCursor)(nil), // 165: common.PreparedQueryCursor + (*LedgerStats)(nil), // 166: common.LedgerStats + (*PersistedConfig)(nil), // 167: common.PersistedConfig + (*CallerIdentity)(nil), // 168: common.CallerIdentity + (*CallerSnapshot)(nil), // 169: common.CallerSnapshot + (*S3StorageConfig)(nil), // 170: common.S3StorageConfig + (*AzureStorageConfig)(nil), // 171: common.AzureStorageConfig + (*BackupStorage)(nil), // 172: common.BackupStorage + (*ReadOptions)(nil), // 173: common.ReadOptions + (*ListOptions)(nil), // 174: common.ListOptions + nil, // 175: common.MetadataMap.ValuesEntry + nil, // 176: common.Transaction.MetadataEntry + nil, // 177: common.Script.VarsEntry + nil, // 178: common.VolumesByAssets.VolumesEntry + nil, // 179: common.PostCommitVolumes.VolumesByAccountEntry + nil, // 180: common.Account.MetadataEntry + nil, // 181: common.Account.VolumesEntry + nil, // 182: common.MetadataSchema.AccountFieldsEntry + nil, // 183: common.MetadataSchema.TransactionFieldsEntry + nil, // 184: common.MetadataSchema.LedgerFieldsEntry + nil, // 185: common.SavedLedgerMetadataLog.MetadataEntry + nil, // 186: common.CreatedLedgerLog.AccountTypesEntry + nil, // 187: common.CreatedTransaction.AccountMetadataEntry + nil, // 188: common.SavedMetadata.MetadataEntry + nil, // 189: common.LedgerInfo.AccountTypesEntry + nil, // 190: common.LedgerInfo.MetadataEntry + nil, // 191: common.SaveMetadataCommand.MetadataEntry + nil, // 192: common.TransactionState.MetadataEntry + nil, // 193: common.IdempotencyFailure.MetadataEntry + nil, // 194: common.AccountType.SegmentTypesEntry + (*signaturepb.SignedLog)(nil), // 195: signature.SignedLog } var file_common_proto_depIdxs = []int32{ - 18, // 0: common.MetadataValue.null_value:type_name -> common.NullValue - 173, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry - 22, // 2: common.Posting.amount:type_name -> common.Uint256 - 23, // 3: common.Transaction.postings:type_name -> common.Posting - 174, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry - 17, // 5: common.Transaction.timestamp:type_name -> common.Timestamp - 17, // 6: common.Transaction.inserted_at:type_name -> common.Timestamp - 17, // 7: common.Transaction.updated_at:type_name -> common.Timestamp - 17, // 8: common.Transaction.reverted_at:type_name -> common.Timestamp - 175, // 9: common.Script.vars:type_name -> common.Script.VarsEntry - 176, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry - 177, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry - 178, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry - 17, // 13: common.Account.first_usage:type_name -> common.Timestamp - 17, // 14: common.Account.insertion_date:type_name -> common.Timestamp - 17, // 15: common.Account.updated_at:type_name -> common.Timestamp - 179, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry - 31, // 17: common.Target.account:type_name -> common.TargetAccount + 19, // 0: common.MetadataValue.null_value:type_name -> common.NullValue + 175, // 1: common.MetadataMap.values:type_name -> common.MetadataMap.ValuesEntry + 23, // 2: common.Posting.amount:type_name -> common.Uint256 + 24, // 3: common.Transaction.postings:type_name -> common.Posting + 176, // 4: common.Transaction.metadata:type_name -> common.Transaction.MetadataEntry + 18, // 5: common.Transaction.timestamp:type_name -> common.Timestamp + 18, // 6: common.Transaction.inserted_at:type_name -> common.Timestamp + 18, // 7: common.Transaction.updated_at:type_name -> common.Timestamp + 18, // 8: common.Transaction.reverted_at:type_name -> common.Timestamp + 177, // 9: common.Script.vars:type_name -> common.Script.VarsEntry + 178, // 10: common.VolumesByAssets.volumes:type_name -> common.VolumesByAssets.VolumesEntry + 179, // 11: common.PostCommitVolumes.volumes_by_account:type_name -> common.PostCommitVolumes.VolumesByAccountEntry + 180, // 12: common.Account.metadata:type_name -> common.Account.MetadataEntry + 18, // 13: common.Account.first_usage:type_name -> common.Timestamp + 18, // 14: common.Account.insertion_date:type_name -> common.Timestamp + 18, // 15: common.Account.updated_at:type_name -> common.Timestamp + 181, // 16: common.Account.volumes:type_name -> common.Account.VolumesEntry + 32, // 17: common.Target.account:type_name -> common.TargetAccount 1, // 18: common.MetadataFieldSchema.type:type_name -> common.MetadataType - 180, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry - 181, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry - 182, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry + 182, // 19: common.MetadataSchema.account_fields:type_name -> common.MetadataSchema.AccountFieldsEntry + 183, // 20: common.MetadataSchema.transaction_fields:type_name -> common.MetadataSchema.TransactionFieldsEntry + 184, // 21: common.MetadataSchema.ledger_fields:type_name -> common.MetadataSchema.LedgerFieldsEntry 0, // 22: common.SetMetadataFieldTypeCommand.target_type:type_name -> common.TargetType 1, // 23: common.SetMetadataFieldTypeCommand.type:type_name -> common.MetadataType 0, // 24: common.MetadataIndexID.target:type_name -> common.TargetType 3, // 25: common.IndexID.tx_builtin:type_name -> common.TransactionBuiltinIndex 5, // 26: common.IndexID.log_builtin:type_name -> common.LogBuiltinIndex 4, // 27: common.IndexID.account_builtin:type_name -> common.AccountBuiltinIndex - 36, // 28: common.IndexID.metadata:type_name -> common.MetadataIndexID - 37, // 29: common.Index.id:type_name -> common.IndexID + 37, // 28: common.IndexID.metadata:type_name -> common.MetadataIndexID + 38, // 29: common.Index.id:type_name -> common.IndexID 2, // 30: common.Index.build_status:type_name -> common.IndexBuildStatus - 17, // 31: common.Index.created_at:type_name -> common.Timestamp - 17, // 32: common.Index.last_built_at:type_name -> common.Timestamp - 42, // 33: common.Log.payload:type_name -> common.LogPayload - 193, // 34: common.Log.response_signature:type_name -> signature.SignedLog - 78, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog - 79, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog - 80, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog - 44, // 38: common.LogPayload.register_signing_key:type_name -> common.RegisteredSigningKeyLog - 45, // 39: common.LogPayload.revoke_signing_key:type_name -> common.RevokedSigningKeyLog - 47, // 40: common.LogPayload.set_signing_config:type_name -> common.SetSigningConfigLog - 48, // 41: common.LogPayload.added_events_sink:type_name -> common.AddedEventsSinkLog - 49, // 42: common.LogPayload.removed_events_sink:type_name -> common.RemovedEventsSinkLog - 94, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog - 95, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog - 96, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog - 97, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog - 50, // 47: common.LogPayload.set_maintenance_mode:type_name -> common.SetMaintenanceModeLog - 54, // 48: common.LogPayload.set_chapter_schedule:type_name -> common.SetChapterScheduleLog - 55, // 49: common.LogPayload.delete_chapter_schedule:type_name -> common.DeletedChapterScheduleLog - 43, // 50: common.LogPayload.promote_ledger:type_name -> common.PromotedLedgerLog - 56, // 51: common.LogPayload.created_prepared_query:type_name -> common.CreatedPreparedQueryLog - 57, // 52: common.LogPayload.updated_prepared_query:type_name -> common.UpdatedPreparedQueryLog - 58, // 53: common.LogPayload.deleted_prepared_query:type_name -> common.DeletedPreparedQueryLog - 62, // 54: common.LogPayload.saved_numscript:type_name -> common.SavedNumscriptLog - 63, // 55: common.LogPayload.deleted_numscript:type_name -> common.DeletedNumscriptLog - 67, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog - 68, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog - 65, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog - 66, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog - 59, // 60: common.LogPayload.saved_ledger_metadata:type_name -> common.SavedLedgerMetadataLog - 60, // 61: common.LogPayload.deleted_ledger_metadata:type_name -> common.DeletedLedgerMetadataLog - 69, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig - 51, // 63: common.ClusterConfig.bloom_volumes:type_name -> common.BloomTypeConfig - 51, // 64: common.ClusterConfig.bloom_metadata:type_name -> common.BloomTypeConfig - 51, // 65: common.ClusterConfig.bloom_references:type_name -> common.BloomTypeConfig - 51, // 66: common.ClusterConfig.bloom_ledgers:type_name -> common.BloomTypeConfig - 51, // 67: common.ClusterConfig.bloom_boundaries:type_name -> common.BloomTypeConfig - 51, // 68: common.ClusterConfig.bloom_transactions:type_name -> common.BloomTypeConfig - 51, // 69: common.ClusterConfig.bloom_sink_configs:type_name -> common.BloomTypeConfig - 51, // 70: common.ClusterConfig.bloom_numscript_versions:type_name -> common.BloomTypeConfig - 51, // 71: common.ClusterConfig.bloom_numscript_contents:type_name -> common.BloomTypeConfig + 18, // 31: common.Index.created_at:type_name -> common.Timestamp + 18, // 32: common.Index.last_built_at:type_name -> common.Timestamp + 43, // 33: common.Log.payload:type_name -> common.LogPayload + 195, // 34: common.Log.response_signature:type_name -> signature.SignedLog + 79, // 35: common.LogPayload.create_ledger:type_name -> common.CreatedLedgerLog + 80, // 36: common.LogPayload.delete_ledger:type_name -> common.DeletedLedgerLog + 81, // 37: common.LogPayload.apply:type_name -> common.ApplyLedgerLog + 45, // 38: common.LogPayload.register_signing_key:type_name -> common.RegisteredSigningKeyLog + 46, // 39: common.LogPayload.revoke_signing_key:type_name -> common.RevokedSigningKeyLog + 48, // 40: common.LogPayload.set_signing_config:type_name -> common.SetSigningConfigLog + 49, // 41: common.LogPayload.added_events_sink:type_name -> common.AddedEventsSinkLog + 50, // 42: common.LogPayload.removed_events_sink:type_name -> common.RemovedEventsSinkLog + 95, // 43: common.LogPayload.close_chapter:type_name -> common.ClosedChapterLog + 96, // 44: common.LogPayload.seal_chapter:type_name -> common.SealedChapterLog + 97, // 45: common.LogPayload.archive_chapter:type_name -> common.ArchivedChapterLog + 98, // 46: common.LogPayload.confirm_archive_chapter:type_name -> common.ConfirmedArchiveChapterLog + 51, // 47: common.LogPayload.set_maintenance_mode:type_name -> common.SetMaintenanceModeLog + 55, // 48: common.LogPayload.set_chapter_schedule:type_name -> common.SetChapterScheduleLog + 56, // 49: common.LogPayload.delete_chapter_schedule:type_name -> common.DeletedChapterScheduleLog + 44, // 50: common.LogPayload.promote_ledger:type_name -> common.PromotedLedgerLog + 57, // 51: common.LogPayload.created_prepared_query:type_name -> common.CreatedPreparedQueryLog + 58, // 52: common.LogPayload.updated_prepared_query:type_name -> common.UpdatedPreparedQueryLog + 59, // 53: common.LogPayload.deleted_prepared_query:type_name -> common.DeletedPreparedQueryLog + 63, // 54: common.LogPayload.saved_numscript:type_name -> common.SavedNumscriptLog + 64, // 55: common.LogPayload.deleted_numscript:type_name -> common.DeletedNumscriptLog + 68, // 56: common.LogPayload.created_query_checkpoint:type_name -> common.CreatedQueryCheckpointLog + 69, // 57: common.LogPayload.deleted_query_checkpoint:type_name -> common.DeletedQueryCheckpointLog + 66, // 58: common.LogPayload.set_query_checkpoint_schedule:type_name -> common.SetQueryCheckpointScheduleLog + 67, // 59: common.LogPayload.delete_query_checkpoint_schedule:type_name -> common.DeletedQueryCheckpointScheduleLog + 60, // 60: common.LogPayload.saved_ledger_metadata:type_name -> common.SavedLedgerMetadataLog + 61, // 61: common.LogPayload.deleted_ledger_metadata:type_name -> common.DeletedLedgerMetadataLog + 70, // 62: common.AddedEventsSinkLog.config:type_name -> common.SinkConfig + 52, // 63: common.ClusterConfig.bloom_volumes:type_name -> common.BloomTypeConfig + 52, // 64: common.ClusterConfig.bloom_metadata:type_name -> common.BloomTypeConfig + 52, // 65: common.ClusterConfig.bloom_references:type_name -> common.BloomTypeConfig + 52, // 66: common.ClusterConfig.bloom_ledgers:type_name -> common.BloomTypeConfig + 52, // 67: common.ClusterConfig.bloom_boundaries:type_name -> common.BloomTypeConfig + 52, // 68: common.ClusterConfig.bloom_transactions:type_name -> common.BloomTypeConfig + 52, // 69: common.ClusterConfig.bloom_sink_configs:type_name -> common.BloomTypeConfig + 52, // 70: common.ClusterConfig.bloom_numscript_versions:type_name -> common.BloomTypeConfig + 52, // 71: common.ClusterConfig.bloom_numscript_contents:type_name -> common.BloomTypeConfig 6, // 72: common.ClusterConfig.hash_algorithm:type_name -> common.HashAlgorithm - 51, // 73: common.ClusterConfig.bloom_ledger_metadata:type_name -> common.BloomTypeConfig - 51, // 74: common.ClusterConfig.bloom_prepared_queries:type_name -> common.BloomTypeConfig - 51, // 75: common.ClusterConfig.bloom_indexes:type_name -> common.BloomTypeConfig - 52, // 76: common.PersistedClusterState.config:type_name -> common.ClusterConfig - 159, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery - 140, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter - 140, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter - 183, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry - 17, // 81: common.NumscriptInfo.created_at:type_name -> common.Timestamp - 61, // 82: common.SavedNumscriptLog.info:type_name -> common.NumscriptInfo - 17, // 83: common.TemplateUsage.last_used:type_name -> common.Timestamp - 72, // 84: common.SinkConfig.nats:type_name -> common.NatsSinkConfig - 73, // 85: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig - 74, // 86: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig - 75, // 87: common.SinkConfig.http:type_name -> common.HttpSinkConfig - 76, // 88: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig + 52, // 73: common.ClusterConfig.bloom_ledger_metadata:type_name -> common.BloomTypeConfig + 52, // 74: common.ClusterConfig.bloom_prepared_queries:type_name -> common.BloomTypeConfig + 52, // 75: common.ClusterConfig.bloom_indexes:type_name -> common.BloomTypeConfig + 53, // 76: common.PersistedClusterState.config:type_name -> common.ClusterConfig + 161, // 77: common.CreatedPreparedQueryLog.query:type_name -> common.PreparedQuery + 141, // 78: common.UpdatedPreparedQueryLog.previous_filter:type_name -> common.QueryFilter + 141, // 79: common.UpdatedPreparedQueryLog.new_filter:type_name -> common.QueryFilter + 185, // 80: common.SavedLedgerMetadataLog.metadata:type_name -> common.SavedLedgerMetadataLog.MetadataEntry + 18, // 81: common.NumscriptInfo.created_at:type_name -> common.Timestamp + 62, // 82: common.SavedNumscriptLog.info:type_name -> common.NumscriptInfo + 18, // 83: common.TemplateUsage.last_used:type_name -> common.Timestamp + 73, // 84: common.SinkConfig.nats:type_name -> common.NatsSinkConfig + 74, // 85: common.SinkConfig.clickhouse:type_name -> common.ClickHouseSinkConfig + 75, // 86: common.SinkConfig.kafka:type_name -> common.KafkaSinkConfig + 76, // 87: common.SinkConfig.http:type_name -> common.HttpSinkConfig + 77, // 88: common.SinkConfig.databricks:type_name -> common.DatabricksSinkConfig 7, // 89: common.SinkConfig.event_types:type_name -> common.EventType - 71, // 90: common.SinkStatus.error:type_name -> common.SinkError - 17, // 91: common.SinkError.occurred_at:type_name -> common.Timestamp - 77, // 92: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M - 17, // 93: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp - 34, // 94: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema + 72, // 90: common.SinkStatus.error:type_name -> common.SinkError + 18, // 91: common.SinkError.occurred_at:type_name -> common.Timestamp + 78, // 92: common.DatabricksSinkConfig.oauth_m2m:type_name -> common.DatabricksOAuthM2M + 18, // 93: common.CreatedLedgerLog.created_at:type_name -> common.Timestamp + 35, // 94: common.CreatedLedgerLog.metadata_schema:type_name -> common.MetadataSchema 9, // 95: common.CreatedLedgerLog.mode:type_name -> common.LedgerMode - 98, // 96: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig - 184, // 97: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry + 99, // 96: common.CreatedLedgerLog.mirror_source:type_name -> common.MirrorSourceConfig + 186, // 97: common.CreatedLedgerLog.account_types:type_name -> common.CreatedLedgerLog.AccountTypesEntry 12, // 98: common.CreatedLedgerLog.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 17, // 99: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp - 81, // 100: common.ApplyLedgerLog.log:type_name -> common.LedgerLog - 83, // 101: common.LedgerLog.data:type_name -> common.LedgerLogPayload - 17, // 102: common.LedgerLog.date:type_name -> common.Timestamp - 82, // 103: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume - 82, // 104: common.LedgerLog.new_volumes:type_name -> common.TouchedVolume - 87, // 105: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction - 88, // 106: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction - 89, // 107: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata - 90, // 108: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata - 91, // 109: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog - 92, // 110: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog - 86, // 111: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog - 84, // 112: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog - 85, // 113: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog - 137, // 114: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog - 138, // 115: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog - 139, // 116: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog - 37, // 117: common.CreatedIndexLog.id:type_name -> common.IndexID - 37, // 118: common.DroppedIndexLog.id:type_name -> common.IndexID - 24, // 119: common.CreatedTransaction.transaction:type_name -> common.Transaction - 185, // 120: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry - 29, // 121: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 24, // 122: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction - 29, // 123: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes - 32, // 124: common.SavedMetadata.target:type_name -> common.Target - 186, // 125: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry - 32, // 126: common.DeletedMetadata.target:type_name -> common.Target - 0, // 127: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 1, // 128: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType - 0, // 129: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType - 37, // 130: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID - 17, // 131: common.Chapter.start:type_name -> common.Timestamp - 17, // 132: common.Chapter.end:type_name -> common.Timestamp - 8, // 133: common.Chapter.status:type_name -> common.ChapterStatus - 93, // 134: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter - 93, // 135: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter - 93, // 136: common.SealedChapterLog.chapter:type_name -> common.Chapter - 93, // 137: common.ArchivedChapterLog.chapter:type_name -> common.Chapter - 93, // 138: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter - 118, // 139: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig - 120, // 140: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig - 99, // 141: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule - 100, // 142: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule - 101, // 143: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule - 102, // 144: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule - 103, // 145: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule - 104, // 146: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule - 105, // 147: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction - 106, // 148: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction - 107, // 149: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction - 108, // 150: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction - 109, // 151: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction - 110, // 152: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 153: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 154: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 113, // 155: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction - 114, // 156: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction - 115, // 157: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction - 117, // 158: common.CreatedTransactionAction.drop:type_name -> common.DropAction - 110, // 159: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 160: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 161: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction - 117, // 162: common.RevertedTransactionAction.drop:type_name -> common.DropAction - 110, // 163: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 111, // 164: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction - 112, // 165: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction - 117, // 166: common.SavedMetadataAction.drop:type_name -> common.DropAction - 110, // 167: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction - 117, // 168: common.DeletedMetadataAction.drop:type_name -> common.DropAction - 110, // 169: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction - 117, // 170: common.AnyVariantAction.drop:type_name -> common.DropAction - 116, // 171: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement - 119, // 172: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials - 121, // 173: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth - 17, // 174: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp - 10, // 175: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState - 122, // 176: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError - 17, // 177: common.LedgerInfo.created_at:type_name -> common.Timestamp - 17, // 178: common.LedgerInfo.deleted_at:type_name -> common.Timestamp - 34, // 179: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema - 9, // 180: common.LedgerInfo.mode:type_name -> common.LedgerMode - 98, // 181: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig - 123, // 182: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress - 187, // 183: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry - 12, // 184: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode - 188, // 185: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry - 32, // 186: common.SaveMetadataCommand.target:type_name -> common.Target - 189, // 187: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry - 32, // 188: common.DeleteMetadataCommand.target:type_name -> common.Target - 190, // 189: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry - 17, // 190: common.TransactionState.timestamp:type_name -> common.Timestamp - 23, // 191: common.TransactionState.postings:type_name -> common.Posting - 17, // 192: common.TransactionState.reverted_at:type_name -> common.Timestamp - 129, // 193: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure - 11, // 194: common.IdempotencyFailure.reason:type_name -> common.ErrorReason - 191, // 195: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry - 133, // 196: common.SegmentType.uuid:type_name -> common.UUIDConstraint - 134, // 197: common.SegmentType.uint64:type_name -> common.Uint64Constraint - 135, // 198: common.SegmentType.bytes:type_name -> common.BytesConstraint - 13, // 199: common.AccountType.persistence:type_name -> common.AccountTypePersistence - 192, // 200: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry - 136, // 201: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType - 12, // 202: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode - 152, // 203: common.QueryFilter.field:type_name -> common.FieldCondition - 158, // 204: common.QueryFilter.address:type_name -> common.AddressMatch - 148, // 205: common.QueryFilter.and:type_name -> common.AndFilter - 149, // 206: common.QueryFilter.or:type_name -> common.OrFilter - 150, // 207: common.QueryFilter.not:type_name -> common.NotFilter - 141, // 208: common.QueryFilter.reference:type_name -> common.ReferenceCondition - 145, // 209: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition - 143, // 210: common.QueryFilter.ledger:type_name -> common.LedgerCondition - 144, // 211: common.QueryFilter.log_id:type_name -> common.LogIdCondition - 146, // 212: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition - 147, // 213: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition - 142, // 214: common.QueryFilter.reverted:type_name -> common.RevertedCondition - 153, // 215: common.ReferenceCondition.cond:type_name -> common.StringCondition - 153, // 216: common.LedgerCondition.cond:type_name -> common.StringCondition - 155, // 217: common.LogIdCondition.cond:type_name -> common.UintCondition - 3, // 218: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex - 155, // 219: common.BuiltinUintCondition.cond:type_name -> common.UintCondition - 5, // 220: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex - 155, // 221: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition - 140, // 222: common.AndFilter.filters:type_name -> common.QueryFilter - 140, // 223: common.OrFilter.filters:type_name -> common.QueryFilter - 140, // 224: common.NotFilter.filter:type_name -> common.QueryFilter - 151, // 225: common.FieldCondition.field:type_name -> common.FieldRef - 153, // 226: common.FieldCondition.string_cond:type_name -> common.StringCondition - 154, // 227: common.FieldCondition.int_cond:type_name -> common.IntCondition - 155, // 228: common.FieldCondition.uint_cond:type_name -> common.UintCondition - 156, // 229: common.FieldCondition.bool_cond:type_name -> common.BoolCondition - 157, // 230: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition - 14, // 231: common.AddressMatch.role:type_name -> common.AddressRole - 140, // 232: common.PreparedQuery.filter:type_name -> common.QueryFilter - 15, // 233: common.PreparedQuery.target:type_name -> common.QueryTarget - 22, // 234: common.AggregatedVolume.input:type_name -> common.Uint256 - 22, // 235: common.AggregatedVolume.output:type_name -> common.Uint256 - 160, // 236: common.AggregateResult.volumes:type_name -> common.AggregatedVolume - 162, // 237: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult - 160, // 238: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume - 30, // 239: common.PreparedQueryCursor.account_data:type_name -> common.Account - 24, // 240: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction - 166, // 241: common.CallerSnapshot.identity:type_name -> common.CallerIdentity - 168, // 242: common.BackupStorage.s3:type_name -> common.S3StorageConfig - 169, // 243: common.BackupStorage.azure:type_name -> common.AzureStorageConfig - 171, // 244: common.ListOptions.read:type_name -> common.ReadOptions - 140, // 245: common.ListOptions.filter:type_name -> common.QueryFilter - 19, // 246: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue - 19, // 247: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue - 26, // 248: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes - 28, // 249: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets - 19, // 250: common.Account.MetadataEntry.value:type_name -> common.MetadataValue - 27, // 251: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance - 33, // 252: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema - 33, // 253: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema - 33, // 254: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema - 19, // 255: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue - 136, // 256: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType - 20, // 257: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap - 19, // 258: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue - 136, // 259: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType - 19, // 260: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue - 19, // 261: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue - 19, // 262: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue - 132, // 263: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType - 264, // [264:264] is the sub-list for method output_type - 264, // [264:264] is the sub-list for method input_type - 264, // [264:264] is the sub-list for extension type_name - 264, // [264:264] is the sub-list for extension extendee - 0, // [0:264] is the sub-list for field type_name + 18, // 99: common.DeletedLedgerLog.deleted_at:type_name -> common.Timestamp + 82, // 100: common.ApplyLedgerLog.log:type_name -> common.LedgerLog + 84, // 101: common.LedgerLog.data:type_name -> common.LedgerLogPayload + 18, // 102: common.LedgerLog.date:type_name -> common.Timestamp + 83, // 103: common.LedgerLog.purged_volumes:type_name -> common.TouchedVolume + 83, // 104: common.LedgerLog.new_kept_volumes:type_name -> common.TouchedVolume + 83, // 105: common.LedgerLog.ephemeral_volumes:type_name -> common.TouchedVolume + 88, // 106: common.LedgerLogPayload.created_transaction:type_name -> common.CreatedTransaction + 89, // 107: common.LedgerLogPayload.reverted_transaction:type_name -> common.RevertedTransaction + 90, // 108: common.LedgerLogPayload.saved_metadata:type_name -> common.SavedMetadata + 91, // 109: common.LedgerLogPayload.deleted_metadata:type_name -> common.DeletedMetadata + 92, // 110: common.LedgerLogPayload.set_metadata_field_type:type_name -> common.SetMetadataFieldTypeLog + 93, // 111: common.LedgerLogPayload.removed_metadata_field_type:type_name -> common.RemovedMetadataFieldTypeLog + 87, // 112: common.LedgerLogPayload.fill_gap:type_name -> common.FilledGapLog + 85, // 113: common.LedgerLogPayload.create_index:type_name -> common.CreatedIndexLog + 86, // 114: common.LedgerLogPayload.drop_index:type_name -> common.DroppedIndexLog + 138, // 115: common.LedgerLogPayload.added_account_type:type_name -> common.AddedAccountTypeLog + 139, // 116: common.LedgerLogPayload.removed_account_type:type_name -> common.RemovedAccountTypeLog + 140, // 117: common.LedgerLogPayload.updated_default_enforcement_mode:type_name -> common.UpdatedDefaultEnforcementModeLog + 38, // 118: common.CreatedIndexLog.id:type_name -> common.IndexID + 38, // 119: common.DroppedIndexLog.id:type_name -> common.IndexID + 25, // 120: common.CreatedTransaction.transaction:type_name -> common.Transaction + 187, // 121: common.CreatedTransaction.account_metadata:type_name -> common.CreatedTransaction.AccountMetadataEntry + 30, // 122: common.CreatedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 25, // 123: common.RevertedTransaction.revert_transaction:type_name -> common.Transaction + 30, // 124: common.RevertedTransaction.post_commit_volumes:type_name -> common.PostCommitVolumes + 33, // 125: common.SavedMetadata.target:type_name -> common.Target + 188, // 126: common.SavedMetadata.metadata:type_name -> common.SavedMetadata.MetadataEntry + 33, // 127: common.DeletedMetadata.target:type_name -> common.Target + 0, // 128: common.SetMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 1, // 129: common.SetMetadataFieldTypeLog.type:type_name -> common.MetadataType + 0, // 130: common.RemovedMetadataFieldTypeLog.target_type:type_name -> common.TargetType + 38, // 131: common.RemovedMetadataFieldTypeLog.dropped_index:type_name -> common.IndexID + 18, // 132: common.Chapter.start:type_name -> common.Timestamp + 18, // 133: common.Chapter.end:type_name -> common.Timestamp + 8, // 134: common.Chapter.status:type_name -> common.ChapterStatus + 94, // 135: common.ClosedChapterLog.closed_chapter:type_name -> common.Chapter + 94, // 136: common.ClosedChapterLog.new_chapter:type_name -> common.Chapter + 94, // 137: common.SealedChapterLog.chapter:type_name -> common.Chapter + 94, // 138: common.ArchivedChapterLog.chapter:type_name -> common.Chapter + 94, // 139: common.ConfirmedArchiveChapterLog.chapter:type_name -> common.Chapter + 119, // 140: common.MirrorSourceConfig.http:type_name -> common.HttpMirrorSourceConfig + 121, // 141: common.MirrorSourceConfig.postgres:type_name -> common.PostgresMirrorSourceConfig + 100, // 142: common.MirrorSourceConfig.rewrite_rules:type_name -> common.MirrorRewriteRule + 101, // 143: common.MirrorRewriteRule.created_transaction:type_name -> common.CreatedTransactionRule + 102, // 144: common.MirrorRewriteRule.reverted_transaction:type_name -> common.RevertedTransactionRule + 103, // 145: common.MirrorRewriteRule.saved_metadata:type_name -> common.SavedMetadataRule + 104, // 146: common.MirrorRewriteRule.deleted_metadata:type_name -> common.DeletedMetadataRule + 105, // 147: common.MirrorRewriteRule.any_variant:type_name -> common.AnyVariantRule + 106, // 148: common.CreatedTransactionRule.actions:type_name -> common.CreatedTransactionAction + 107, // 149: common.RevertedTransactionRule.actions:type_name -> common.RevertedTransactionAction + 108, // 150: common.SavedMetadataRule.actions:type_name -> common.SavedMetadataAction + 109, // 151: common.DeletedMetadataRule.actions:type_name -> common.DeletedMetadataAction + 110, // 152: common.AnyVariantRule.actions:type_name -> common.AnyVariantAction + 111, // 153: common.CreatedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 154: common.CreatedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 155: common.CreatedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 114, // 156: common.CreatedTransactionAction.set_account_metadata:type_name -> common.SetAccountMetadataAction + 115, // 157: common.CreatedTransactionAction.delete_account_metadata:type_name -> common.DeleteAccountMetadataAction + 116, // 158: common.CreatedTransactionAction.set_account_metadata_from_address:type_name -> common.SetAccountMetadataFromAddressAction + 118, // 159: common.CreatedTransactionAction.drop:type_name -> common.DropAction + 111, // 160: common.RevertedTransactionAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 161: common.RevertedTransactionAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 162: common.RevertedTransactionAction.delete_metadata:type_name -> common.DeleteMetadataAction + 118, // 163: common.RevertedTransactionAction.drop:type_name -> common.DropAction + 111, // 164: common.SavedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 112, // 165: common.SavedMetadataAction.set_metadata:type_name -> common.SetMetadataAction + 113, // 166: common.SavedMetadataAction.delete_metadata:type_name -> common.DeleteMetadataAction + 118, // 167: common.SavedMetadataAction.drop:type_name -> common.DropAction + 111, // 168: common.DeletedMetadataAction.rewrite_address:type_name -> common.RewriteAddressAction + 118, // 169: common.DeletedMetadataAction.drop:type_name -> common.DropAction + 111, // 170: common.AnyVariantAction.rewrite_address:type_name -> common.RewriteAddressAction + 118, // 171: common.AnyVariantAction.drop:type_name -> common.DropAction + 117, // 172: common.SetAccountMetadataFromAddressAction.replacements:type_name -> common.SetAccountMetadataFromAddressReplacement + 120, // 173: common.HttpMirrorSourceConfig.oauth2_client_credentials:type_name -> common.OAuth2ClientCredentials + 122, // 174: common.PostgresMirrorSourceConfig.aws_iam_auth:type_name -> common.PostgresAwsIamAuth + 18, // 175: common.MirrorSyncError.occurred_at:type_name -> common.Timestamp + 10, // 176: common.MirrorSyncProgress.state:type_name -> common.MirrorSyncState + 123, // 177: common.MirrorSyncProgress.error:type_name -> common.MirrorSyncError + 18, // 178: common.LedgerInfo.created_at:type_name -> common.Timestamp + 18, // 179: common.LedgerInfo.deleted_at:type_name -> common.Timestamp + 35, // 180: common.LedgerInfo.metadata_schema:type_name -> common.MetadataSchema + 9, // 181: common.LedgerInfo.mode:type_name -> common.LedgerMode + 99, // 182: common.LedgerInfo.mirror_source:type_name -> common.MirrorSourceConfig + 124, // 183: common.LedgerInfo.mirror_sync_progress:type_name -> common.MirrorSyncProgress + 189, // 184: common.LedgerInfo.account_types:type_name -> common.LedgerInfo.AccountTypesEntry + 12, // 185: common.LedgerInfo.default_enforcement_mode:type_name -> common.ChartEnforcementMode + 190, // 186: common.LedgerInfo.metadata:type_name -> common.LedgerInfo.MetadataEntry + 33, // 187: common.SaveMetadataCommand.target:type_name -> common.Target + 191, // 188: common.SaveMetadataCommand.metadata:type_name -> common.SaveMetadataCommand.MetadataEntry + 33, // 189: common.DeleteMetadataCommand.target:type_name -> common.Target + 192, // 190: common.TransactionState.metadata:type_name -> common.TransactionState.MetadataEntry + 18, // 191: common.TransactionState.timestamp:type_name -> common.Timestamp + 24, // 192: common.TransactionState.postings:type_name -> common.Posting + 18, // 193: common.TransactionState.reverted_at:type_name -> common.Timestamp + 130, // 194: common.IdempotencyKeyValue.failure:type_name -> common.IdempotencyFailure + 11, // 195: common.IdempotencyFailure.reason:type_name -> common.ErrorReason + 193, // 196: common.IdempotencyFailure.metadata:type_name -> common.IdempotencyFailure.MetadataEntry + 134, // 197: common.SegmentType.uuid:type_name -> common.UUIDConstraint + 135, // 198: common.SegmentType.uint64:type_name -> common.Uint64Constraint + 136, // 199: common.SegmentType.bytes:type_name -> common.BytesConstraint + 13, // 200: common.AccountType.persistence:type_name -> common.AccountTypePersistence + 194, // 201: common.AccountType.segment_types:type_name -> common.AccountType.SegmentTypesEntry + 137, // 202: common.AddedAccountTypeLog.account_type:type_name -> common.AccountType + 12, // 203: common.UpdatedDefaultEnforcementModeLog.enforcement_mode:type_name -> common.ChartEnforcementMode + 154, // 204: common.QueryFilter.field:type_name -> common.FieldCondition + 160, // 205: common.QueryFilter.address:type_name -> common.AddressMatch + 150, // 206: common.QueryFilter.and:type_name -> common.AndFilter + 151, // 207: common.QueryFilter.or:type_name -> common.OrFilter + 152, // 208: common.QueryFilter.not:type_name -> common.NotFilter + 142, // 209: common.QueryFilter.reference:type_name -> common.ReferenceCondition + 147, // 210: common.QueryFilter.builtin_uint:type_name -> common.BuiltinUintCondition + 145, // 211: common.QueryFilter.ledger:type_name -> common.LedgerCondition + 146, // 212: common.QueryFilter.log_id:type_name -> common.LogIdCondition + 148, // 213: common.QueryFilter.log_builtin_uint:type_name -> common.LogBuiltinUintCondition + 149, // 214: common.QueryFilter.account_has_asset:type_name -> common.AccountHasAssetCondition + 143, // 215: common.QueryFilter.reverted:type_name -> common.RevertedCondition + 144, // 216: common.QueryFilter.audit:type_name -> common.AuditCondition + 155, // 217: common.ReferenceCondition.cond:type_name -> common.StringCondition + 14, // 218: common.AuditCondition.field:type_name -> common.AuditField + 155, // 219: common.AuditCondition.string_cond:type_name -> common.StringCondition + 157, // 220: common.AuditCondition.uint_cond:type_name -> common.UintCondition + 155, // 221: common.LedgerCondition.cond:type_name -> common.StringCondition + 157, // 222: common.LogIdCondition.cond:type_name -> common.UintCondition + 3, // 223: common.BuiltinUintCondition.field:type_name -> common.TransactionBuiltinIndex + 157, // 224: common.BuiltinUintCondition.cond:type_name -> common.UintCondition + 5, // 225: common.LogBuiltinUintCondition.field:type_name -> common.LogBuiltinIndex + 157, // 226: common.LogBuiltinUintCondition.cond:type_name -> common.UintCondition + 141, // 227: common.AndFilter.filters:type_name -> common.QueryFilter + 141, // 228: common.OrFilter.filters:type_name -> common.QueryFilter + 141, // 229: common.NotFilter.filter:type_name -> common.QueryFilter + 153, // 230: common.FieldCondition.field:type_name -> common.FieldRef + 155, // 231: common.FieldCondition.string_cond:type_name -> common.StringCondition + 156, // 232: common.FieldCondition.int_cond:type_name -> common.IntCondition + 157, // 233: common.FieldCondition.uint_cond:type_name -> common.UintCondition + 158, // 234: common.FieldCondition.bool_cond:type_name -> common.BoolCondition + 159, // 235: common.FieldCondition.exists_cond:type_name -> common.ExistsCondition + 15, // 236: common.AddressMatch.role:type_name -> common.AddressRole + 141, // 237: common.PreparedQuery.filter:type_name -> common.QueryFilter + 16, // 238: common.PreparedQuery.target:type_name -> common.QueryTarget + 23, // 239: common.AggregatedVolume.input:type_name -> common.Uint256 + 23, // 240: common.AggregatedVolume.output:type_name -> common.Uint256 + 162, // 241: common.AggregateResult.volumes:type_name -> common.AggregatedVolume + 164, // 242: common.AggregateResult.groups:type_name -> common.GroupedAggregateResult + 162, // 243: common.GroupedAggregateResult.volumes:type_name -> common.AggregatedVolume + 31, // 244: common.PreparedQueryCursor.account_data:type_name -> common.Account + 25, // 245: common.PreparedQueryCursor.transaction_data:type_name -> common.Transaction + 168, // 246: common.CallerSnapshot.identity:type_name -> common.CallerIdentity + 170, // 247: common.BackupStorage.s3:type_name -> common.S3StorageConfig + 171, // 248: common.BackupStorage.azure:type_name -> common.AzureStorageConfig + 173, // 249: common.ListOptions.read:type_name -> common.ReadOptions + 141, // 250: common.ListOptions.filter:type_name -> common.QueryFilter + 20, // 251: common.MetadataMap.ValuesEntry.value:type_name -> common.MetadataValue + 20, // 252: common.Transaction.MetadataEntry.value:type_name -> common.MetadataValue + 27, // 253: common.VolumesByAssets.VolumesEntry.value:type_name -> common.Volumes + 29, // 254: common.PostCommitVolumes.VolumesByAccountEntry.value:type_name -> common.VolumesByAssets + 20, // 255: common.Account.MetadataEntry.value:type_name -> common.MetadataValue + 28, // 256: common.Account.VolumesEntry.value:type_name -> common.VolumesWithBalance + 34, // 257: common.MetadataSchema.AccountFieldsEntry.value:type_name -> common.MetadataFieldSchema + 34, // 258: common.MetadataSchema.TransactionFieldsEntry.value:type_name -> common.MetadataFieldSchema + 34, // 259: common.MetadataSchema.LedgerFieldsEntry.value:type_name -> common.MetadataFieldSchema + 20, // 260: common.SavedLedgerMetadataLog.MetadataEntry.value:type_name -> common.MetadataValue + 137, // 261: common.CreatedLedgerLog.AccountTypesEntry.value:type_name -> common.AccountType + 21, // 262: common.CreatedTransaction.AccountMetadataEntry.value:type_name -> common.MetadataMap + 20, // 263: common.SavedMetadata.MetadataEntry.value:type_name -> common.MetadataValue + 137, // 264: common.LedgerInfo.AccountTypesEntry.value:type_name -> common.AccountType + 20, // 265: common.LedgerInfo.MetadataEntry.value:type_name -> common.MetadataValue + 20, // 266: common.SaveMetadataCommand.MetadataEntry.value:type_name -> common.MetadataValue + 20, // 267: common.TransactionState.MetadataEntry.value:type_name -> common.MetadataValue + 133, // 268: common.AccountType.SegmentTypesEntry.value:type_name -> common.SegmentType + 269, // [269:269] is the sub-list for method output_type + 269, // [269:269] is the sub-list for method input_type + 269, // [269:269] is the sub-list for extension type_name + 269, // [269:269] is the sub-list for extension extendee + 0, // [0:269] is the sub-list for field type_name } func init() { file_common_proto_init() } @@ -13908,36 +14137,41 @@ func file_common_proto_init() { (*QueryFilter_LogBuiltinUint)(nil), (*QueryFilter_AccountHasAsset)(nil), (*QueryFilter_Reverted)(nil), + (*QueryFilter_Audit)(nil), + } + file_common_proto_msgTypes[126].OneofWrappers = []any{ + (*AuditCondition_StringCond)(nil), + (*AuditCondition_UintCond)(nil), } - file_common_proto_msgTypes[135].OneofWrappers = []any{ + file_common_proto_msgTypes[136].OneofWrappers = []any{ (*FieldCondition_StringCond)(nil), (*FieldCondition_IntCond)(nil), (*FieldCondition_UintCond)(nil), (*FieldCondition_BoolCond)(nil), (*FieldCondition_ExistsCond)(nil), } - file_common_proto_msgTypes[136].OneofWrappers = []any{ + file_common_proto_msgTypes[137].OneofWrappers = []any{ (*StringCondition_Hardcoded)(nil), (*StringCondition_Param)(nil), } - file_common_proto_msgTypes[137].OneofWrappers = []any{} file_common_proto_msgTypes[138].OneofWrappers = []any{} - file_common_proto_msgTypes[139].OneofWrappers = []any{ + file_common_proto_msgTypes[139].OneofWrappers = []any{} + file_common_proto_msgTypes[140].OneofWrappers = []any{ (*BoolCondition_Hardcoded)(nil), (*BoolCondition_Param)(nil), } - file_common_proto_msgTypes[141].OneofWrappers = []any{ + file_common_proto_msgTypes[142].OneofWrappers = []any{ (*AddressMatch_HardcodedPrefix)(nil), (*AddressMatch_HardcodedExact)(nil), (*AddressMatch_ParamPrefix)(nil), (*AddressMatch_ParamExact)(nil), } - file_common_proto_msgTypes[149].OneofWrappers = []any{ + file_common_proto_msgTypes[150].OneofWrappers = []any{ (*CallerIdentity_Issuer)(nil), (*CallerIdentity_KeyId)(nil), (*CallerIdentity_SystemComponent)(nil), } - file_common_proto_msgTypes[153].OneofWrappers = []any{ + file_common_proto_msgTypes[154].OneofWrappers = []any{ (*BackupStorage_S3)(nil), (*BackupStorage_Azure)(nil), } @@ -13946,8 +14180,8 @@ func file_common_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)), - NumEnums: 17, - NumMessages: 176, + NumEnums: 18, + NumMessages: 177, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/proto/commonpb/common_dethash.pb.go b/internal/proto/commonpb/common_dethash.pb.go index 26edd9b2f7..80e1195500 100644 --- a/internal/proto/commonpb/common_dethash.pb.go +++ b/internal/proto/commonpb/common_dethash.pb.go @@ -1660,9 +1660,18 @@ func (m *LedgerLog) MarshalToSizedBufferDeterministicVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.NewVolumes) > 0 { - for iNdEx := len(m.NewVolumes) - 1; iNdEx >= 0; iNdEx-- { - size, _ := m.NewVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if len(m.EphemeralVolumes) > 0 { + for iNdEx := len(m.EphemeralVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, _ := m.EphemeralVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + } + if len(m.NewKeptVolumes) > 0 { + for iNdEx := len(m.NewKeptVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, _ := m.NewKeptVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- diff --git a/internal/proto/commonpb/common_reader.pb.go b/internal/proto/commonpb/common_reader.pb.go index c44b6d0aa2..c638c88774 100644 --- a/internal/proto/commonpb/common_reader.pb.go +++ b/internal/proto/commonpb/common_reader.pb.go @@ -5333,7 +5333,8 @@ type LedgerLogReader interface { GetDate() TimestampReader GetId() uint64 GetPurgedVolumes() TouchedVolumeListReader - GetNewVolumes() TouchedVolumeListReader + GetNewKeptVolumes() TouchedVolumeListReader + GetEphemeralVolumes() TouchedVolumeListReader Mutate() *LedgerLog } @@ -5363,8 +5364,12 @@ func (r *ledgerLogReadonly) GetPurgedVolumes() TouchedVolumeListReader { return NewTouchedVolumeListReader((*LedgerLog)(r).GetPurgedVolumes()) } -func (r *ledgerLogReadonly) GetNewVolumes() TouchedVolumeListReader { - return NewTouchedVolumeListReader((*LedgerLog)(r).GetNewVolumes()) +func (r *ledgerLogReadonly) GetNewKeptVolumes() TouchedVolumeListReader { + return NewTouchedVolumeListReader((*LedgerLog)(r).GetNewKeptVolumes()) +} + +func (r *ledgerLogReadonly) GetEphemeralVolumes() TouchedVolumeListReader { + return NewTouchedVolumeListReader((*LedgerLog)(r).GetEphemeralVolumes()) } func (r *ledgerLogReadonly) Mutate() *LedgerLog { diff --git a/internal/proto/commonpb/common_vtproto.pb.go b/internal/proto/commonpb/common_vtproto.pb.go index 723dc5dfe9..7085ee7366 100644 --- a/internal/proto/commonpb/common_vtproto.pb.go +++ b/internal/proto/commonpb/common_vtproto.pb.go @@ -1802,12 +1802,19 @@ func (m *LedgerLog) CloneVT() *LedgerLog { } r.PurgedVolumes = tmpContainer } - if rhs := m.NewVolumes; rhs != nil { + if rhs := m.NewKeptVolumes; rhs != nil { tmpContainer := make([]*TouchedVolume, len(rhs)) for k, v := range rhs { tmpContainer[k] = v.CloneVT() } - r.NewVolumes = tmpContainer + r.NewKeptVolumes = tmpContainer + } + if rhs := m.EphemeralVolumes; rhs != nil { + tmpContainer := make([]*TouchedVolume, len(rhs)) + for k, v := range rhs { + tmpContainer[k] = v.CloneVT() + } + r.EphemeralVolumes = tmpContainer } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -7453,11 +7460,28 @@ func (this *LedgerLog) EqualVT(that *LedgerLog) bool { } } } - if len(this.NewVolumes) != len(that.NewVolumes) { + if len(this.NewKeptVolumes) != len(that.NewKeptVolumes) { + return false + } + for i, vx := range this.NewKeptVolumes { + vy := that.NewKeptVolumes[i] + if p, q := vx, vy; p != q { + if p == nil { + p = &TouchedVolume{} + } + if q == nil { + q = &TouchedVolume{} + } + if !p.EqualVT(q) { + return false + } + } + } + if len(this.EphemeralVolumes) != len(that.EphemeralVolumes) { return false } - for i, vx := range this.NewVolumes { - vy := that.NewVolumes[i] + for i, vx := range this.EphemeralVolumes { + vy := that.EphemeralVolumes[i] if p, q := vx, vy; p != q { if p == nil { p = &TouchedVolume{} @@ -16503,9 +16527,21 @@ func (m *LedgerLog) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.NewVolumes) > 0 { - for iNdEx := len(m.NewVolumes) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.NewVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if len(m.EphemeralVolumes) > 0 { + for iNdEx := len(m.EphemeralVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.EphemeralVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + } + if len(m.NewKeptVolumes) > 0 { + for iNdEx := len(m.NewKeptVolumes) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.NewKeptVolumes[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } @@ -24851,8 +24887,14 @@ func (m *LedgerLog) SizeVT() (n int) { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } } - if len(m.NewVolumes) > 0 { - for _, e := range m.NewVolumes { + if len(m.NewKeptVolumes) > 0 { + for _, e := range m.NewKeptVolumes { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.EphemeralVolumes) > 0 { + for _, e := range m.EphemeralVolumes { l = e.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } @@ -38732,7 +38774,41 @@ func (m *LedgerLog) UnmarshalVT(dAtA []byte) error { iNdEx = postIndex case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NewVolumes", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NewKeptVolumes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewKeptVolumes = append(m.NewKeptVolumes, &TouchedVolume{}) + if err := m.NewKeptVolumes[len(m.NewKeptVolumes)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EphemeralVolumes", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -38759,8 +38835,8 @@ func (m *LedgerLog) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NewVolumes = append(m.NewVolumes, &TouchedVolume{}) - if err := m.NewVolumes[len(m.NewVolumes)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + m.EphemeralVolumes = append(m.EphemeralVolumes, &TouchedVolume{}) + if err := m.EphemeralVolumes[len(m.EphemeralVolumes)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/internal/storage/usagestore/keys.go b/internal/storage/usagestore/keys.go index f310b70561..d1f87d266d 100644 --- a/internal/storage/usagestore/keys.go +++ b/internal/storage/usagestore/keys.go @@ -32,9 +32,9 @@ const ( CounterRevert byte = 0x02 // revert_count — count RevertTransactionOrder + MirrorRevertTransactionOrder CounterNumscriptExecution byte = 0x03 // numscript_execution_count — count CreateTransactionOrder with Script or NumscriptReference CounterReference byte = 0x04 // reference_count — count CreateTransactionOrder with non-empty Reference - CounterEphemeralEvicted byte = 0x05 // ephemeral_evicted_count — sum len(LedgerLog.PurgedVolumes) per log + CounterEphemeralEvicted byte = 0x05 // ephemeral_evicted_count — sum len(LedgerLog.EphemeralVolumes) per log (pure ephemeral: new + purged same log) CounterTransientUsed byte = 0x06 // transient_used_count — sum len(AppliedProposal.TransientVolumes[ledger].Volumes) per proposal - CounterVolume byte = 0x07 // volume_count — sum len(LedgerLog.NewVolumes) - sum len(LedgerLog.PurgedVolumes) per log + CounterVolume byte = 0x07 // volume_count — sum len(LedgerLog.NewKeptVolumes) - len(LedgerLog.PurgedVolumes) per log (ephemeral contributes 0) ) // ProgressKey returns the full key for the usagebuilder progress entry. diff --git a/misc/proto/common.proto b/misc/proto/common.proto index de426007bb..6fc5361290 100644 --- a/misc/proto/common.proto +++ b/misc/proto/common.proto @@ -636,25 +636,31 @@ message LedgerLog { LedgerLogPayload data = 1; Timestamp date = 2; fixed64 id = 3; - // Volumes (account+asset) whose ephemeral state was purged (zero balance) - // by THIS log specifically. Subset of the proposal's purged universe — - // only volumes touched by the order that produced this log. Empty when - // the order did not contribute to any ephemeral purge. The index builder - // uses this to skip account->transaction mappings for the corresponding - // (account, asset) tuples without reading the audit zone. Carrying the - // asset dimension matters: a multi-asset account may have one asset - // purged while another stays kept; tagging the account as a whole would - // over-skip mappings for transactions that touched the kept asset. + // Volumes (account+asset) DRAINED to zero by THIS log — the entry had a + // pre-existing non-zero balance in Pebble and this log brought it back to + // zero, causing the volume to be evicted from the attribute store at + // commit. Purged is DISJOINT from ephemeral_volumes and new_kept_volumes. + // The index builder skips account->transaction mappings for + // purged ∪ ephemeral (both are evicted from Pebble). The usagebuilder + // subtracts len(purged_volumes) from VolumeCount — a draining eviction + // decreases the live cardinality by one. repeated TouchedVolume purged_volumes = 4; - // Volumes (account+asset) whose persistent entry was newly created (never - // seen in the attribute store before) by THIS log. Ephemeral volumes appear - // in BOTH new_volumes AND purged_volumes for the same log: they were - // persisted briefly then evicted after commit. Transient volumes (never - // persisted) are excluded by construction. The usagebuilder derives the - // VolumeCount projection as sum(new_volumes) - sum(purged_volumes) across - // the audit chain; the checker verifies the derived counter against the - // live attribute store. - repeated TouchedVolume new_volumes = 5; + // Volumes (account+asset) whose PERSISTENT entry was newly created by + // THIS log AND SURVIVED past commit. New + kept — disjoint from + // purged_volumes and ephemeral_volumes. The usagebuilder increments + // VolumeCount by len(new_kept_volumes) — a newly-persisted key raises + // the live cardinality by one. + repeated TouchedVolume new_kept_volumes = 5; + // Volumes (account+asset) that were both newly created AND purged by + // THIS log — pure ephemeral. Written to Pebble briefly then evicted at + // commit. Contributes +0 to VolumeCount (was zero, is zero after commit) + // and is tracked separately so: + // - the index builder can skip acct->tx mappings for them (via + // purged_volumes ∪ ephemeral_volumes); + // - the log payload does not duplicate ephemeral tuples in two lists + // (previous encoding carried them in both purged_volumes AND + // new_volumes, doubling bytes on ephemeral-heavy workloads). + repeated TouchedVolume ephemeral_volumes = 6; } // TouchedVolume identifies a (ledger-local) volume cell — an account paired From d6d11ba375cedaaf568d02ca55d5a599e0a840f3 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 10:23:32 +0200 Subject: [PATCH 06/35] docs(usage): dedicated subsystem page under docs/technical/architecture/subsystems/usage/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs/ tree was restructured on release/v3.0 to group architecture documentation by subsystem — each `subsystems//` folder mirrors a code boundary with its own README + topical pages. The usage projection (usagebuilder + usagestore) deserves the same treatment. New folder `docs/technical/architecture/subsystems/usage/`: - README.md — subsystem overview and document index. - usagebuilder.md — pipeline mechanics: wake-up path, boot cursor restore, `processAuditEntries` batched commit, why the audit chain rather than the log stream, snapshot on the reader side, failure semantics, cutover / rebuild story. - counters.md — counter catalogue with source and delta formulae, template usage record shape, log-payload contract (the three disjoint LedgerLog volume-annotation lists — purged / new_kept / ephemeral — introduced by EN-1422 and the ephemeral-heavy motivation for the split), usagestore key layout, downstream consumers (LedgerStats reader / template usage endpoint / indexer / checker), OpenTelemetry metrics. Also update the top-level architecture README index to list the new subsystem next to Storage. --- .../architecture/subsystems/usage/README.md | 19 +++ .../architecture/subsystems/usage/counters.md | 132 +++++++++++++++++ .../subsystems/usage/usagebuilder.md | 137 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 docs/technical/architecture/subsystems/usage/README.md create mode 100644 docs/technical/architecture/subsystems/usage/counters.md create mode 100644 docs/technical/architecture/subsystems/usage/usagebuilder.md diff --git a/docs/technical/architecture/subsystems/usage/README.md b/docs/technical/architecture/subsystems/usage/README.md new file mode 100644 index 0000000000..318ed94bb5 --- /dev/null +++ b/docs/technical/architecture/subsystems/usage/README.md @@ -0,0 +1,19 @@ +# Usage + +The background worker (`internal/application/usagebuilder`) that turns committed audit entries into per-ledger housekeeping counters — postings, reverts, numscript executions, references, ephemeral evictions, transient uses, live volume cardinality — plus per-Numscript-template invocation records. Runs on every node — leader and followers — independently, mirroring the indexer's decoupling from the FSM hot path: the FSM commits and signals; the usagebuilder reads its own Pebble read handle and writes to a dedicated secondary store (`internal/storage/usagestore`). + +The projections it maintains are exposed by the API — `GET /v3/{ledger}/stats` for the aggregate counters, `GET /v3/{ledger}/numscripts/{name}/usage` for per-template invocation counts. + +## Documents + +| Document | Description | +|----------|-------------| +| [usagebuilder.md](usagebuilder.md) | Pipeline mechanics: builder loop, audit-chain tailing, per-batch commit, log-payload consumption. | +| [counters.md](counters.md) | Counter definitions, storage keys, `LedgerLog.{purged,new_kept,ephemeral}_volumes` split, formula for each counter. | + +## Related + +- [FSM](../fsm/) — emits the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` annotations the usagebuilder consumes. Depends on the volume preload contract (see AGENTS.md invariant #6). +- [Indexer](../indexer/) — sibling subsystem with the same audit-tailing shape, but writes to a different secondary store (`readstore`). +- [Checker](../checker/) — verifies the projections against the audit chain (`compareExclusionProjections` for ephemeral/transient tuples). +- [Storage](../storage/) — the usage store is a peer Pebble instance to the readstore, WAL disabled, own comparer, own Pebble tuning. diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md new file mode 100644 index 0000000000..1681d2bd48 --- /dev/null +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -0,0 +1,132 @@ +# Counters and Storage Schema + +This page documents the projections the usagebuilder materialises: what each counter counts, how the FSM plumbs the source data through the log payload, how the counters are keyed in the usagestore, and how the checker verifies them. + +For the pipeline that populates these keys, see [usagebuilder.md](usagebuilder.md). + +## Counter Catalogue + +Every counter is keyed by `(ledger, counter_id)` in the usagestore (`internal/storage/usagestore/keys.go`) — one byte per counter, stable on-disk identifiers, never renumbered. + +| ID | Name | Source | Delta per event | +|----|------|--------|-----------------| +| `0x01` | `CounterPosting` | `Transaction.Postings` (`CreatedTransaction`) + `RevertTransaction.Postings` (`RevertedTransaction`) | `+len(Postings)` per applicable log | +| `0x02` | `CounterRevert` | `RevertTransactionOrder` unmarshalled from `AuditItem.SerializedOrder` (covers both direct and mirror-ingested reverts) | `+1` per order | +| `0x03` | `CounterNumscriptExecution` | `CreateTransactionOrder` with a non-empty `Script.Plain` or a non-nil `NumscriptReference` | `+1` per order | +| `0x04` | `CounterReference` | `CreateTransactionOrder.Reference != ""` | `+1` per order | +| `0x05` | `CounterEphemeralEvicted` | `len(LedgerLog.EphemeralVolumes)` per log — the pure-ephemeral tuples (new + purged same log) | `+len(EphemeralVolumes)` | +| `0x06` | `CounterTransientUsed` | `len(AppliedProposal.TransientVolumes[ledger].Volumes)` — batch-level, keyed by audit sequence | `+len(TransientVolumes[ledger])` per proposal | +| `0x07` | `CounterVolume` | `len(LedgerLog.NewKeptVolumes) − len(LedgerLog.PurgedVolumes)` per log | net change in live volume-key cardinality | + +Missing keys read as `0`. Every counter clamps at zero on underflow via `applyDelta` — a subsystem bug that emits a spurious `−1` cannot drive the counter into an out-of-band representation. + +## Template Usage + +Per-Numscript-template records live under a separate key shape: + +``` +[usagestore prefix][ledger 64B][template_name] → TemplateUsage { fixed64 count; Timestamp last_used } +``` + +Populated when a `CreateTransactionOrder` carries a non-nil `NumscriptReference`. `last_used` is the order's `Timestamp` (deterministic — same on every replica). Multiple invocations in the same batch fold via max on `last_used` and sum on `count`. + +Read path: `GET /v3/{ledger}/numscripts/{name}/usage` (`internal/adapter/http/handlers_get_numscript_usage.go`) — returns the persisted `TemplateUsage` proto or a zero-valued one when the template has never been invoked (not a 404). + +## The Log-Payload Contract + +Six of the seven counters plus template usage need FSM-side annotations on the log or the audit chain. Two categories: + +- **Straight from the raw order** (posting, revert, numscript-exec, reference, template usage): no FSM enrichment needed — the fields are already on `raftcmdpb.CreateTransactionOrder` / `RevertTransactionOrder`, which the usagebuilder unmarshalls from `AuditItem.SerializedOrder`. +- **Volume annotations on the log** (ephemeral evicted, volume count): the FSM computes three DISJOINT per-log lists during `WriteSet.Merge` and injects them into the `LedgerLog` message. + +### `LedgerLog` volume annotations + +`misc/proto/common.proto` — three disjoint fields on `LedgerLog`: + +```protobuf +message LedgerLog { + LedgerLogPayload data = 1; + Timestamp date = 2; + fixed64 id = 3; + repeated TouchedVolume purged_volumes = 4; // DRAINING only + repeated TouchedVolume new_kept_volumes = 5; // new + survives + repeated TouchedVolume ephemeral_volumes = 6; // new + purged same log +} +``` + +The three sets partition every volume update the log touched into DISJOINT categories: + +| Category | Prior value | Post-commit state | Field | +|----------|-------------|-------------------|-------| +| Draining | non-zero in Pebble | evicted (zero balance) | `purged_volumes` | +| New + kept | undefined or zero placeholder | persisted | `new_kept_volumes` | +| Pure ephemeral | undefined or zero placeholder | evicted (zero balance) | `ephemeral_volumes` | +| Normal update | defined + non-zero | still persisted (updated value) | (none — no annotation needed) | +| Transient | any | never persisted | (none — carried on `AppliedProposal.TransientVolumes` at batch level) | + +### Why disjoint (and not overlapping) + +An earlier encoding included ephemeral tuples in BOTH `purged_volumes` (as "evicted") and `new_volumes` (as "newly created"). Every ephemeral (account, asset) tuple paid its wire cost twice. On workloads with high ephemeral throughput (payout fan-out, escrow pass-throughs — 100+ ephemeral accounts per transaction is normal), the doubling adds a few MB/s of WAL / audit-chain growth for no functional benefit — the ephemeral net delta on `VolumeCount` is +0. + +The disjoint encoding pays exactly `len(ephemeral)` per log instead of `2 × len(ephemeral)`. The three lists are still complete: consumers that need "everything evicted from Pebble" take the union (see [indexer consumer](#indexer-consumer)); the volume-count formula becomes clean subtraction (`new_kept − purged`) with ephemeral contributing zero. + +### FSM emission + +`internal/infra/state/write_set.go` computes the three sets during `Merge`: + +1. `partitionVolumes(volumeUpdates)` (existing) yields `partResult.{kept, purged, transient}`. +2. `splitPurged(partResult.purged)` (new, in `write_set_new_volumes.go`) partitions `purged` further into: + - **ephemeral**: `!Old.IsDefined() || isVolumePreloadZero(Old.Value())` — the key had no prior state, was touched, immediately purged. + - **draining**: `Old.IsDefined() && !isVolumePreloadZero(Old.Value())` — had a prior non-zero balance, drained to zero, evicted. +3. `makeNewKeptKeySet(partResult.kept)` (new) yields the subset of `kept` where `Old` was undefined / zero-placeholder — i.e. new persistent volumes. +4. `buildTouchedByLog(volumes.Slots(), setX)` (new, generalised from the previous `buildPurgedByLog`) intersects the per-order touched-volume tracking with each of the three sets to produce the per-log annotation lists. Deduplication + deterministic sort by (account, asset) keeps the log payload byte-identical across nodes. + +The three lists are injected into each `LedgerLog` inside the same `createdLogs` build loop that also injects `purged_volumes`. The audit hash chain covers the whole `LedgerLog` so any drift is detected by the checker. + +### The preload contract this depends on + +The classification "new vs existing" is decided by the preloaded prior value at merge time — specifically `Update.Old.IsDefined()` combined with the zero-placeholder check. This is safe **because volume preload is structurally required by the FSM**: balance checks, Uint256 arithmetic and numscript resolution all read the current volume value, so admission has to preload every touched key. That contract is documented as invariant #6 in AGENTS.md. + +The comparable metadata preload was removed opportunistically once the indexer no longer needed it — the corresponding `MetadataCount` counter had to be dropped (see the EN-1420 commit) because `Old.IsDefined()` no longer distinguished "new key" from "overwrite" on the metadata merge. The volume analog holds because the FSM cannot function without those old values. + +## Usagestore Layout + +``` +[template_prefix][ledger 64B][template_name] → TemplateUsage proto +[counter_prefix][ledger 64B][counter_id 1B] → uint64 BE +[0xFE][0x01] → progress cursor (uint64 BE, last consumed audit sequence) +``` + +Full keyspace conventions in `internal/storage/usagestore/keys.go`. The `[ledger 64B]` block is zero-padded fixed-width (`dal.LedgerNameFixedSize`) so the comparer can extract a per-ledger prefix for bloom-filter scoping — same technique as the readstore. + +## Consumers + +### API — `GetLedgerStats` reader + +`internal/application/ctrl/controller_default.go` — opens a single `usagestore.Snapshot` and routes all seven counter Gets through it. See [usagebuilder.md § Snapshot on the reader side](usagebuilder.md#snapshot-on-the-reader-side). + +### API — template usage endpoint + +`internal/adapter/http/handlers_get_numscript_usage.go` → `ctrl.Controller.GetTemplateUsage` → `usagestore.GetTemplateUsage`. Single point-read against the live store (no snapshot needed for a single-value query). + +### Indexer consumer + +`internal/application/indexbuilder/applied_proposal_sync.go`'s `extractPurgedVolumes` returns the UNION of `LedgerLog.PurgedVolumes ∪ LedgerLog.EphemeralVolumes` because both categories share the same downstream treatment (Pebble entry gone → skip acct→tx mapping). The protowire fast path (`protowire_postings.go`) parses field 6 alongside field 4 and exposes `GetEphemeralVolumes` on `parsedLog`. + +### Checker consumer + +`compareExclusionProjections` (`internal/application/check/checker.go`) accumulates `PurgedVolumes` and `EphemeralVolumes` from every log into the stored projection set, then compares against the exclusion set derived by replaying the audit chain (`AppliedProposal.TransientVolumes` union). Both eviction lists have identical semantics for the checker — the split is a pure log-payload compaction, not an invariant change. + +A dedicated checker pass for `CounterVolume` (replay `sum(new_kept − purged)` and compare against the usagestore value) is a follow-up — the template exists in `compareExclusionProjections`. + +## Metrics + +Registered in `misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet`: + +| Metric | Description | +|--------|-------------| +| `usage.builder.last_processed_audit_sequence` | Highest audit sequence the builder has committed for this replica. | +| `usage.builder.pebble_last_audit_sequence` | Highest audit sequence present in Pebble on this replica. | +| `usage.builder.lag` | Difference between the two (indicator of the eventual-consistency window). | +| `usage.builder.entries_processed_total` | Cumulative audit entries consumed since process start. | +| `usagestore.level.bytes` / `memtable.bytes` / `cache.hits` / `cache.misses` | Pebble-internal metrics for the usagestore instance (parallel to the readindex namespace). | diff --git a/docs/technical/architecture/subsystems/usage/usagebuilder.md b/docs/technical/architecture/subsystems/usage/usagebuilder.md new file mode 100644 index 0000000000..7961f42e02 --- /dev/null +++ b/docs/technical/architecture/subsystems/usage/usagebuilder.md @@ -0,0 +1,137 @@ +# Usagebuilder Pipeline + +## Overview + +The **usagebuilder** (`internal/application/usagebuilder`) is the background goroutine that turns committed audit entries into per-ledger housekeeping counters + per-Numscript-template invocation records. It runs on every node, tails the FSM audit chain, and writes to the **usagestore** — a Pebble instance dedicated to usage projections, peer to the read store. + +This page covers the pipeline mechanics. For the **what** of counters (definitions, storage keys, log-payload plumbing), see [counters.md](counters.md). + +## Why the audit chain, not the log stream + +The subsystem tails `AuditEntry + AuditItem` (`ZoneCold` / `SubColdAudit` + `SubColdAuditItem`), not `ZoneCold` / `SubColdLog`. The reason is Numscript template usage: the log's `CreatedTransaction` payload does not carry the `NumscriptReference` of the order — only the resolved postings and metadata. The reference survives on `AuditItem.SerializedOrder`, which is the deterministic serialised bytes of the original `raftcmdpb.Order`. Reading the audit chain lets the usagebuilder unmarshal the order, extract `NumscriptReference.Name` for template tracking, and decide whether the order should feed the numscript-execution counter. + +For counters that live on the log directly (posting count, purge lists), the usagebuilder fetches the specific log at `AuditItem.LogSequence` via `query.ReadLogBySequence` — a single point read on the hot Pebble cache. + +## Builder Lifecycle + +### Wake-up + +```mermaid +flowchart LR + FSM["FSM Commit
infra/state/machine.go"] -->|NotifyLogsCommitted| SIG["signal.Notifications.LogCommitted
name:\"usage\" FanOut target"] + SIG -->|select case| LOOP["Builder.loop()"] + TICK["100 ms Ticker"] -->|fallback case| LOOP +``` + +| Trigger | Source | +|---------|--------| +| FSM commit signal | `signal.Notifications.LogCommitted.C()` — fired by the FSM via `NotifyLogsCommitted(lastSeq)`. The FanOut in `bootstrap/module.go` dispatches to a dedicated `name:"usage"` `Notifications` so the usagebuilder does not compete with the indexer, events, or mirror consumers. | +| 100 ms fallback ticker | `time.NewTicker(100 * time.Millisecond)` in `Builder.loop()`. Same rationale as the indexer's — bound query staleness even if the signal layer is starved. | +| Cancellation | `ctx.Done()` (wired through `worker.Worker`). | + +### Constructor injection for notifications + +Unlike the indexer (which uses `SetNotifications` after construction), the usagebuilder receives its `*signal.Notifications` through the `NewBuilder` constructor — the fx graph passes the correctly-tagged `Notifications` at wire time. See `feedback_constructor_injection` in the project memory: setters create a hidden init protocol that constructor parameters do not. + +### Boot + +On `Start()`, the builder: + +1. Reads the persisted progress cursor from `usagestore` (key `[0xFE][0x01]` — highest processed audit sequence). +2. Samples the current audit-chain head via `query.ReadLastAuditSequence` on a short-lived read handle. The handle is closed immediately to release the `dbMu.RLock` so a concurrent `RestoreCheckpoint` is not blocked while the builder is idle. +3. Runs an **initial catch-up pass** with a larger batch size (`max(configured, 2_000)`), split into 5-second slices so a single Pebble snapshot is not held for the full catch-up duration on large stores. Between slices the snapshot is released; between passes cache-warmed reads keep it cheap. + +## `processAuditEntries` — Batched Commit + +```mermaid +flowchart TB + A[Open Pebble read handle on FSM main store] --> B[Open audit-chain cursor at persisted seq] + B --> C{Iterate AuditEntry} + C -->|Success outcome| D[Fetch AppliedProposal by audit_seq] + C -->|Failure outcome| C + D --> E[Feed TransientVolumes → CounterTransientUsed] + E --> F[Read AuditItems for entry] + F --> G{Iterate items} + G -->|LogSequence == 0| G + G -->|non-zero| H[Unmarshal SerializedOrder → raftcmdpb.Order] + H --> I[dispatchOrder — switch on Apply payload] + I --> J[Fetch log via ReadLogBySequence for posting / volume counts] + J --> K[Accumulate counter + template deltas in batchState] + K --> G + G -->|EOF for entry| C + C -->|batchCount reached OR EOF| L[commitBatch: apply deltas + advance cursor] +``` + +`internal/application/usagebuilder/process_audit.go`. + +### Pass 1 — collect + +- Opens a direct Pebble read handle on the FSM main store (`b.pebbleStore.NewDirectReadHandle()`). +- Opens an audit-entry cursor at `cursor + 1` (`query.ReadAuditEntries(ctx, handle, &cursor)`). +- For each entry: + - Failure outcomes are skipped — no state change to project. + - For successful entries, fetches the matching `AppliedProposal` (`query.ReadAppliedProposal(ctx, handle, entry.GetSequence())`) once. Its `TransientVolumes` map (per-ledger) feeds `CounterTransientUsed` — a single per-entry Get that avoids re-reading it per item. + - Reads all `AuditItem` rows for the entry (`query.ReadAuditItems`). + - For each item with `LogSequence != 0` (skipping idempotent replays and non-log-producing orders), unmarshals `SerializedOrder` and dispatches on the order variant. `dispatchCreateTransaction` and `dispatchRevertTransaction` fetch the produced log via `ReadLogBySequence` to extract resolved posting count + the three volume-annotation lists — see [counters.md](counters.md) for the fields. +- All deltas accumulate into a per-batch `batchState` (per-ledger counter map + per-(ledger, template) usage map). + +### Pass 2 — commit + +`commitBatch` does read-modify-write against the usagestore in a single Pebble batch: + +1. For each `(ledger, counterID)` in the batch state, `Get` the current counter, apply the signed delta with `applyDelta` (clamps at zero on underflow), `PutCounter` the new value. +2. For each `(ledger, template)` template delta, `Get` the current `TemplateUsage` proto, fold the batch aggregation into it (add counts, take max of `last_used` timestamps), `PutTemplateUsage`. +3. `WriteProgress(batch, lastAuditSeq)` — persists the cursor in the **same batch** as the counter mutations. +4. `batch.Commit()`. + +The invariant is the same as the indexer's two-pass commit: cursor and counter deltas move atomically. A crash mid-batch either commits both or neither — the loop resumes cleanly on restart. + +### Batch sizing + +Default is 200 audit entries per commit (`DefaultBatchSize` in `builder.go`). Higher during initial catch-up (`max(configured, 2_000)`). The trade-off is Pebble fsync frequency vs snapshot lifetime — the same one the indexer solves with `catchUpBudget = 5 * time.Second`. + +## Snapshot on the reader side + +`GetLedgerStats` reads seven counters (posting, revert, numscript-exec, reference, ephemeral, transient, volume) plus optional template-usage records. If each `GetCounter` opened its own Pebble read, a concurrent usagebuilder commit could land between two `Get`s and produce a partial view (e.g. VolumeCount reflects a commit that PostingCount does not). To avoid this the API opens a single `usagestore.Snapshot` and routes all reads through it: + +```go +snap := ctrl.usageStore.NewSnapshot() +defer snap.Close() +stats.PostingCount, _ = snap.GetCounter(ledger, usagestore.CounterPosting) +// … all six other event counters read from the same snap +``` + +`Snapshot` wraps `*pebble.Snapshot` and re-exposes the read helpers. Writes stay batched atomically on the usagebuilder side; reads see a consistent point-in-time view. + +## Failure semantics + +| Failure mode | What survives | What replays | +|--------------|---------------|--------------| +| Usagebuilder crash mid-batch | Cursor at last successful commit; committed counter deltas are durable | Loop restarts, reads persisted cursor, resumes from `cursor + 1`. | +| Cold-storage archival of an early chapter | Nothing lost — counters already applied by the usagebuilder are persisted in usagestore | Cursor stays past the archived range; no re-processing. | +| Ledger deletion (audit log `DeleteLedger`) | Usagestore range-deletes every counter + template row keyed on the ledger via `DeleteLedger(batch, ledgerName)` | Re-created ledgers start at zero counters; audit-chain history for the old incarnation is idempotent to re-process. | +| Full rebuild via `ledgerctl store rebuild-usage` | Nothing — the usagestore directory is dropped | Cursor resets to 0; the builder replays the entire reachable audit chain into a fresh store. See [rebuild-usage](../../../ops/cli.md#store-rebuild-usage). | + +## Cutover semantics + +The migration that introduced this subsystem (EN-1420 / EN-1422) moved every non-ID-generator counter off `LedgerBoundaries` and onto the usagestore. On production upgrade, each ledger's counters **reset to 0** and repopulate from the earliest audit entry still reachable in Pebble. Historic pre-upgrade values are lost — accepted trade-off. Fresh ledgers boot with a genesis-derived count that matches the FSM. + +## Snapshot / restore + +The usagestore is not part of Pebble snapshots or backups: it is a projection that is trivially rebuildable from the audit chain. On restore, the operator can either: + +- Let the running usagebuilder catch up organically from wherever its persisted cursor points, or +- Run `ledgerctl store rebuild-usage` offline before restart to drop-and-repopulate. + +The audit chain remains the source of truth in both cases. + +## Summary + +| Concern | Mechanism | File | +|---------|-----------|------| +| Wake-up | Dedicated `name:"usage"` `Notifications` from the FSM FanOut + 100 ms ticker | `builder.go` | +| Source | Audit chain (`ReadAuditEntries` + `ReadAuditItems`), not the log stream — needed for Numscript template ref survival | `process_audit.go` | +| Atomicity | `WriteProgress` shares the same Pebble batch as counter / template mutations | `process_audit.go`, `usagestore/store.go` | +| Read consistency | Multi-counter reads via `usagestore.NewSnapshot()` | `usagestore/snapshot.go`, `ctrl/controller_default.go` | +| Isolation | Dedicated Pebble instance at `/usage/`, own comparer, WAL disabled | `usagestore/{store,comparer,keys}.go` | +| Rebuild | `ledgerctl store rebuild-usage` — drop directory + replay | `cmd/ledgerctl/store/rebuild_usage.go` | From c5a11f86c52637a52e45ac617a23a830b3dfe9dc Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 10:45:01 +0200 Subject: [PATCH 07/35] fix(EN-1422): dedup volume tuples per audit entry + adapt transient test to the split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two failing e2e tests surfaced after the Option B split (draining / new-kept / pure ephemeral on separate LedgerLog fields): ## GetLedgerStats — VolumeCount over-counts shared accounts `stats_test.go` batches three transactions into one Apply: tx1: world → bank:main tx2: bank:main → bank:fees tx3: bank:main → users:alice Merge produces 4 distinct volume updates: (world, bank:main, bank:fees, users:alice). All four have `Old` undefined at the batch boundary, so all four are "new + kept" → VolumeCount delta = +4. But the FSM emits the per-log annotation lists using each ORDER's touched volume slots. `bank:main` appears in slots[tx1] (dest), slots[tx2] (source) and slots[tx3] (source) — so the intersection with the new-kept set puts it in THREE per-log lists. The usagebuilder used to sum `len(NewKeptVolumes)` per log and got 6 instead of 4. Fix: dedup per audit entry. An audit entry maps 1:1 to an FSM apply batch, so a `(ledger, account, asset)` tuple can only change persistence class at most once inside it. Introduce `entryVolumeState` — three seen sets that reset at each audit entry — and gate every CounterVolume / CounterEphemeralEvicted increment on first-touch semantics. Postings, references, reverts and numscript executions stay per-event (no dedup — each order is a distinct event). ## TransientAccounts test — pure ephemeral is now in EphemeralVolumes Before the Option B split, `LedgerLog.PurgedVolumes` carried both draining evictions and pure ephemeral tuples. The test collected `ll.GetPurgedVolumes()` and asserted `(clearing:ep1, USD)` — which is pure ephemeral — was present. After the split, that tuple moved to `ll.GetEphemeralVolumes()`. The indexbuilder was already updated to union both fields (`applied_proposal_sync.go:extractPurgedVolumes`); the test just needed the same treatment. ## Refactor - `logCounts` (int-only) renamed to `logVolumeAnnotations` and now carries the actual `[]*TouchedVolume` slices — the dedup step needs per-tuple identity, not counts. - `countsFromLog` renamed to `readLog` to reflect that it returns raw annotations, not derived numbers. - `applyVolumeDelta` becomes `applyVolumeAnnotations` and takes the entry-scoped seen sets. --- .../application/usagebuilder/process_audit.go | 166 ++++++++++++------ tests/e2e/business/transient_accounts_test.go | 12 +- 2 files changed, 118 insertions(+), 60 deletions(-) diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 43c3bced3a..5cf56c8c7c 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -180,6 +180,11 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli return cursor, fmt.Errorf("reading audit items for seq %d: %w", entry.GetSequence(), err) } + // Fresh dedup sets per audit entry — the entry is the natural + // boundary at which a (ledger, account, asset) key can change + // persistence class at most once. See applyVolumeAnnotations. + entryState := newEntryVolumeState() + for _, item := range items { // LogSequence == 0 → idempotent replay or non-log-producing // order (metadata schema changes, etc.). Skip: no work. @@ -193,7 +198,7 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli entry.GetSequence(), item.GetOrderIndex(), err) } - if err := b.dispatchOrder(ctx, handle, order, item.GetLogSequence(), state); err != nil { + if err := b.dispatchOrder(ctx, handle, order, item.GetLogSequence(), state, entryState); err != nil { return cursor, err } } @@ -246,13 +251,15 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli // dispatchOrder inspects a raw Order and accumulates counter / template // deltas into the batch state. Fetches the produced log when the resolved -// posting count is required (revert txs, script-backed create txs). +// posting count is required (revert txs, script-backed create txs). entry +// carries the per-audit-entry dedup scratchpad — see applyVolumeAnnotations. func (b *Builder) dispatchOrder( ctx context.Context, handle dal.PebbleGetter, order *raftcmdpb.Order, logSeq uint64, state *batchState, + entry *entryVolumeState, ) error { scoped := order.GetLedgerScoped() if scoped == nil { @@ -275,24 +282,76 @@ func (b *Builder) dispatchOrder( switch data := apply.GetData().(type) { case *raftcmdpb.LedgerApplyOrder_CreateTransaction: - return b.dispatchCreateTransaction(ctx, handle, ledger, data.CreateTransaction, logSeq, state) + return b.dispatchCreateTransaction(ctx, handle, ledger, data.CreateTransaction, logSeq, state, entry) case *raftcmdpb.LedgerApplyOrder_RevertTransaction: - return b.dispatchRevertTransaction(ctx, handle, ledger, logSeq, state) + return b.dispatchRevertTransaction(ctx, handle, ledger, logSeq, state, entry) } return nil } -// applyVolumeDelta feeds CounterVolume with the new-kept minus draining -// delta from a single log. Pure ephemeral volumes live in their own -// EphemeralVolumes list on the log and contribute +0 to VolumeCount -// (was zero, is zero after commit — never counted). draining volumes -// (in PurgedVolumes) had a prior balance and are evicted from Pebble -// at commit, so they subtract from the live cardinality. -func applyVolumeDelta(ledger string, counts logCounts, state *batchState) { - delta := counterDelta(counts.newKept) - counterDelta(counts.purged) - if delta != 0 { - state.addCounter(ledger, usagestore.CounterVolume, delta) +// entryVolumeState is the per-audit-entry deduplication scratchpad. Each set +// records the (account, asset) tuples already applied to their respective +// counter for the current audit entry. VolumeCount, CounterEphemeralEvicted +// and CounterTransientUsed are per-batch cardinality deltas: a tuple that +// appears in multiple orders of the same batch (e.g. a shared "bank:main" +// account touched by three transactions) must contribute at most once to +// each counter. Postings / references / reverts / numscript executions are +// per-event and don't need this dedup. +type entryVolumeState struct { + seenNewKept map[volumeSetKey]struct{} + seenPurged map[volumeSetKey]struct{} + seenEphemeral map[volumeSetKey]struct{} +} + +// volumeSetKey mirrors state.volumeSetKey — kept local to avoid crossing +// package boundaries just for a triple of strings. +type volumeSetKey struct { + ledger string + account string + asset string +} + +func newEntryVolumeState() *entryVolumeState { + return &entryVolumeState{ + seenNewKept: make(map[volumeSetKey]struct{}), + seenPurged: make(map[volumeSetKey]struct{}), + seenEphemeral: make(map[volumeSetKey]struct{}), + } +} + +// applyVolumeAnnotations folds the three disjoint per-log volume lists into +// the batch counter state, deduplicating each tuple within the current audit +// entry. The audit entry maps 1:1 to an FSM apply batch, so a tuple can only +// change persistence class (new → kept, draining → evicted, ephemeral in-out) +// at most once inside it. Without this dedup, an account touched by N orders +// of the same batch would be counted N times. +func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batchState, entry *entryVolumeState) { + for _, v := range ann.newKept { + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + if _, ok := entry.seenNewKept[k]; ok { + continue + } + entry.seenNewKept[k] = struct{}{} + state.addCounter(ledger, usagestore.CounterVolume, 1) + } + + for _, v := range ann.purged { + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + if _, ok := entry.seenPurged[k]; ok { + continue + } + entry.seenPurged[k] = struct{}{} + state.addCounter(ledger, usagestore.CounterVolume, -1) + } + + for _, v := range ann.ephemeral { + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + if _, ok := entry.seenEphemeral[k]; ok { + continue + } + entry.seenEphemeral[k] = struct{}{} + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, 1) } } @@ -306,24 +365,21 @@ func (b *Builder) dispatchCreateTransaction( order *raftcmdpb.CreateTransactionOrder, logSeq uint64, state *batchState, + entry *entryVolumeState, ) error { - // Resolved posting count and purged-volume count live on the log — the + // Resolved posting count and volume annotations live on the log — the // order carries raw postings only for the non-scripted path, and never // carries purge info. - counts, err := b.countsFromLog(ctx, handle, logSeq) + ann, err := b.readLog(ctx, handle, logSeq) if err != nil { return err } - if counts.postings > 0 { - state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) + if ann.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) } - if counts.ephemeral > 0 { - state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.ephemeral)) - } - - applyVolumeDelta(ledger, counts, state) + applyVolumeAnnotations(ledger, ann, state, entry) if order.GetReference() != "" { state.addCounter(ledger, usagestore.CounterReference, 1) @@ -352,72 +408,68 @@ func (b *Builder) dispatchRevertTransaction( ledger string, logSeq uint64, state *batchState, + entry *entryVolumeState, ) error { state.addCounter(ledger, usagestore.CounterRevert, 1) - counts, err := b.countsFromLog(ctx, handle, logSeq) + ann, err := b.readLog(ctx, handle, logSeq) if err != nil { return err } - if counts.postings > 0 { - state.addCounter(ledger, usagestore.CounterPosting, counterDelta(counts.postings)) - } - - if counts.ephemeral > 0 { - state.addCounter(ledger, usagestore.CounterEphemeralEvicted, counterDelta(counts.ephemeral)) + if ann.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) } - applyVolumeDelta(ledger, counts, state) + applyVolumeAnnotations(ledger, ann, state, entry) return nil } -// logCounts is the tuple of per-log counter deltas extracted from a single -// LedgerLog payload. purged / newKept / ephemeral are the three disjoint -// volume-annotation lists on LedgerLog — see the proto comments for the -// invariants they satisfy. -type logCounts struct { +// logVolumeAnnotations bundles the three disjoint TouchedVolume lists that +// LedgerLog carries plus the resolved posting count. The lists are kept as +// slices (not lengths) because the counter dispatch needs per-tuple identity +// for batch-scoped deduplication — see the applyVolumeDelta docstring. +type logVolumeAnnotations struct { postings int - purged int // len(LedgerLog.PurgedVolumes) — draining only - newKept int // len(LedgerLog.NewKeptVolumes) — new + kept - ephemeral int // len(LedgerLog.EphemeralVolumes) — new + purged + purged []*commonpb.TouchedVolume // len — draining only + newKept []*commonpb.TouchedVolume // new + kept + ephemeral []*commonpb.TouchedVolume // new + purged (pure ephemeral) } -// countsFromLog fetches the log at logSeq and returns the resolved posting -// count plus the number of ephemerally-purged volumes attached to this -// specific log. Both are zero if the log does not exist or carries no -// relevant payload. -func (b *Builder) countsFromLog(ctx context.Context, handle dal.PebbleGetter, logSeq uint64) (logCounts, error) { +// readLog fetches the log at logSeq and returns its posting count plus the +// three disjoint volume-annotation lists. Empty when the log does not exist +// or carries no transaction / annotation. +func (b *Builder) readLog(ctx context.Context, handle dal.PebbleGetter, logSeq uint64) (logVolumeAnnotations, error) { log, err := query.ReadLogBySequence(ctx, handle, logSeq) if err != nil { - return logCounts{}, fmt.Errorf("reading log at seq %d: %w", logSeq, err) + return logVolumeAnnotations{}, fmt.Errorf("reading log at seq %d: %w", logSeq, err) } if log == nil { - return logCounts{}, nil + return logVolumeAnnotations{}, nil } apply, ok := log.GetPayload().GetType().(*commonpb.LogPayload_Apply) if !ok || apply.Apply == nil { - return logCounts{}, nil + return logVolumeAnnotations{}, nil } ledgerLog := apply.Apply.GetLog() if ledgerLog == nil { - return logCounts{}, nil + return logVolumeAnnotations{}, nil } // PurgedVolumes / NewKeptVolumes / EphemeralVolumes live on LedgerLog - // directly (not on the payload variant), so we can extract them - // independently of the payload type. The three lists are DISJOINT: pure - // ephemeral tuples appear only in EphemeralVolumes (not duplicated - // across Purged + New), keeping the log payload compact on - // ephemeral-heavy workloads. - result := logCounts{ - purged: len(ledgerLog.GetPurgedVolumes()), - newKept: len(ledgerLog.GetNewKeptVolumes()), - ephemeral: len(ledgerLog.GetEphemeralVolumes()), + // directly (not on the payload variant). The three lists are DISJOINT + // at the FSM emission site, but a single (account, asset) key can + // still appear in multiple orders' lists within the SAME batch because + // each order tracks the volumes IT touched. The counter side of the + // pipeline deduplicates per audit entry — see applyVolumeDelta. + result := logVolumeAnnotations{ + purged: ledgerLog.GetPurgedVolumes(), + newKept: ledgerLog.GetNewKeptVolumes(), + ephemeral: ledgerLog.GetEphemeralVolumes(), } if ledgerLog.GetData() == nil { diff --git a/tests/e2e/business/transient_accounts_test.go b/tests/e2e/business/transient_accounts_test.go index 0b2b31962b..c46b913de5 100644 --- a/tests/e2e/business/transient_accounts_test.go +++ b/tests/e2e/business/transient_accounts_test.go @@ -148,8 +148,11 @@ var _ = Describe("TransientAccounts", Ordered, func() { Expect(logs).NotTo(BeEmpty()) // At least one of the two logs in the batch must carry - // {Account: clearing:ep1, Asset: USD} in its purged_volumes. - // The index builder uses this tuple to skip the matching + // {Account: clearing:ep1, Asset: USD} in its eviction lists. + // Pure ephemeral tuples (was zero, briefly touched, is zero) + // live in LedgerLog.EphemeralVolumes; draining evictions + // (was non-zero, back to zero) live in LedgerLog.PurgedVolumes. + // The index builder unions both to skip the matching // account->transaction mapping while preserving any other // asset's mappings on the same account. type touched struct{ Account, Asset string } @@ -163,10 +166,13 @@ var _ = Describe("TransientAccounts", Ordered, func() { for _, v := range ll.GetPurgedVolumes() { purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) } + for _, v := range ll.GetEphemeralVolumes() { + purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) + } } } Expect(purged).To(ContainElement(touched{Account: "clearing:ep1", Asset: "USD"}), - "at least one log in the batch should list (clearing:ep1, USD) as purged") + "at least one log in the batch should list (clearing:ep1, USD) as evicted") }) }) From 5c6884ecf59122a6df35282171383614dafce86a Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 2 Jul 2026 11:03:58 +0200 Subject: [PATCH 08/35] fix(EN-1334): address PR review findings on usagebuilder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round of fixes for the legitimate findings from the automated review. Not fixed: (a) attribute-derived counter reset on upgrade — accepted trade-off documented at the ticket level; (b) checker verification pass for the usagestore projections — deferred as per the original commit message, tracked separately. ## Fast-path atomic gate removed (builder.go) The `signal.Notifications.LastSequence` atomic tracks the last LOG sequence, but the usagebuilder's cursor tracks the last AUDIT sequence. Comparing them directly was a category error: a failed proposal advances the audit chain without producing a log, so the fast path could leave audit entries un-projected. Removed — `processAuditEntries` opens an audit-scoped cursor and hits EOF immediately when nothing new has landed, so the pebble-side cost of a spurious wake-up is negligible. ## Mirror-ingest and DeleteLedger orders now dispatched `scoped.GetApply() == nil` used to blanket-return early, silently dropping two order variants that DO carry state changes: - `MirrorIngestOrder` — produces the same CreatedTransaction / RevertedTransaction logs the direct write path emits, so posting / revert / volume / ephemeral counters all apply. Reference / numscript-execution / template-usage are absent on the mirror wire (no client metadata travels across it) and stay unchanged. - `DeleteLedgerOrder` — flags the ledger for range-delete in the usagestore. commitBatch runs `usagestore.DeleteLedger(batch, ledger)` after counter mutations so the cascade sees a clean point-in-time view. Same pattern the readstore uses for DeleteLedgerIndexes. ## Ledger existence check on GetTemplateUsage An unknown or soft-deleted ledger now surfaces a NotFound business error rather than a zero-valued 200. Matches the contract of GetLedgerStats / GetNumscript. ## Template-usage HTTP DTO `writeOK(w, *commonpb.TemplateUsage)` was leaking the wire encoding: snake-case field tags (`last_used`) and the raw Timestamp struct (`{data: }`). Introduce a `templateUsageJSON` DTO with camelCase tags and RFC3339 formatting for `lastUsed`, matching the OpenAPI contract and the pattern already used by `ledgerStatsJSON`. The DTO omits `lastUsed` entirely when the counter has never advanced (zero timestamp) so the "never invoked" response stays lean. ## lastUsed falls back to the log timestamp For a numscript order where the client omits `timestamp`, `order.GetTimestamp()` returns nil at audit-processing time even though the FSM has stamped a proposal-date timestamp on the produced log. `readLog` now returns `txTimestamp` extracted from the log's Transaction; `dispatchCreateTransaction` uses it as a fallback so `lastUsed` is always populated on a successful invocation. ## Historical (checkpointed) GetLedgerStats When `WithStores(store, readStore)` clones the controller for a checkpoint read, the clone still owned the LIVE usagestore. Serving LIVE usage counters alongside checkpointed boundary values produced a non-deterministic view of the same checkpoint id. The clone now sets `historical = true` and GetLedgerStats returns zero usage counters in that mode — the usagestore doesn't participate in query checkpoints, and pretending otherwise would give clients incorrect data. ## rebuild-usage guards against wiping the primary store Introduce `ensureDisjointDirs` — the command RemoveAlls the usage directory before opening the primary Pebble store, so an operator who passes `--usage-dir` equal to (or a parent of) `--data-dir` would wipe the live store. Comparison is on cleaned absolute paths so relative forms, trailing separators and symbolic paths all normalise. --- cmd/ledgerctl/store/rebuild_usage.go | 35 ++++ .../http/handlers_get_numscript_usage.go | 35 +++- .../application/ctrl/controller_default.go | 36 ++++- internal/application/usagebuilder/builder.go | 19 ++- .../application/usagebuilder/process_audit.go | 150 +++++++++++++++--- 5 files changed, 240 insertions(+), 35 deletions(-) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index ae9853e6b7..02d0f049a4 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -17,6 +18,32 @@ import ( "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) +// ensureDisjointDirs rejects overlapping --data-dir / --usage-dir values — +// the rebuild command RemoveAlls the usage dir before opening the data dir, +// so an operator who passes the same path (or a parent) would wipe the live +// Pebble store. Comparison is done on cleaned absolute paths so relative +// forms, trailing separators and symbolic paths all normalise before the +// prefix check. +func ensureDisjointDirs(dataDir, usageDir string) error { + absData, err := filepath.Abs(dataDir) + if err != nil { + return fmt.Errorf("resolving --data-dir: %w", err) + } + absUsage, err := filepath.Abs(usageDir) + if err != nil { + return fmt.Errorf("resolving --usage-dir: %w", err) + } + + if absData == absUsage { + return fmt.Errorf("--usage-dir (%s) must not equal --data-dir — running this command would delete the primary Pebble store", absUsage) + } + if strings.HasPrefix(absData+string(filepath.Separator), absUsage+string(filepath.Separator)) { + return fmt.Errorf("--usage-dir (%s) must not be a parent of --data-dir (%s) — running this command would delete the primary Pebble store", absUsage, absData) + } + + return nil +} + // NewRebuildUsageCommand creates the store rebuild-usage command. func NewRebuildUsageCommand() *cobra.Command { cmd := &cobra.Command{ @@ -59,6 +86,14 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { usageDir = filepath.Join(dataDir, "usage") } + // Guard against an operator passing --usage-dir equal to or inside + // --data-dir: the RemoveAll below would then wipe (part of) the live + // Pebble store before we ever open it read-only. Same category of + // footgun as running `rm -rf` on a mount point. + if err := ensureDisjointDirs(dataDir, usageDir); err != nil { + return cmdutil.Displayed(err) + } + logger := logging.NopZap() // Drop the existing usage store so the builder starts at cursor=0 and diff --git a/internal/adapter/http/handlers_get_numscript_usage.go b/internal/adapter/http/handlers_get_numscript_usage.go index a400ddcb19..8543d052ce 100644 --- a/internal/adapter/http/handlers_get_numscript_usage.go +++ b/internal/adapter/http/handlers_get_numscript_usage.go @@ -3,15 +3,44 @@ package http import ( "errors" "net/http" + "time" "github.com/go-chi/chi/v5" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" ) +// templateUsageJSON is the camelCase JSON DTO for TemplateUsage. It +// matches the OpenAPI contract for `GET /v3/{ledger}/numscripts/{name}/usage`: +// +// { "count": , "lastUsed": "" } +// +// Emitting the raw protobuf struct would leak snake_case field tags +// (`last_used`) and the wire encoding of Timestamp (`{data: }`) — +// neither of which is what the API contract promises. +type templateUsageJSON struct { + Count uint64 `json:"count"` + LastUsed *string `json:"lastUsed,omitempty"` +} + +func toTemplateUsageJSON(usage *commonpb.TemplateUsage) *templateUsageJSON { + out := &templateUsageJSON{Count: usage.GetCount()} + + if ts := usage.GetLastUsed(); ts != nil && ts.GetData() != 0 { + formatted := time.Unix(0, int64(ts.GetData())).UTC().Format(time.RFC3339Nano) + out.LastUsed = &formatted + } + + return out +} + // handleGetNumscriptUsage handles GET /{ledgerName}/numscripts/{name}/usage. // Returns the invocation counter + last-used timestamp populated by the // usagebuilder subsystem. Values are eventually consistent with the FSM -// (may lag by up to one usagebuilder tick). A never-invoked template -// returns a zero-valued response, not a 404 — clients treat 0 uniformly. +// (may lag by up to one usagebuilder tick). A never-invoked template on +// an existing ledger returns a zero-valued response, not a 404 — clients +// treat 0 uniformly. Unknown / soft-deleted ledgers surface a 404 (via +// the underlying controller). func (s *Server) handleGetNumscriptUsage(w http.ResponseWriter, r *http.Request) { ledgerName, ok := requireLedgerName(w, r) if !ok { @@ -32,5 +61,5 @@ func (s *Server) handleGetNumscriptUsage(w http.ResponseWriter, r *http.Request) return } - writeOK(w, usage) + writeOK(w, toTemplateUsageJSON(usage)) } diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index 098e3e137b..5fc85b6c43 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -101,6 +101,13 @@ type DefaultController struct { usageStore *usagestore.Store coldReader *coldstorage.ColdReader + // historical is true on clones produced by WithStores — reads are then + // served from a point-in-time checkpoint. usage counters are excluded + // from historical responses because the usagestore does not participate + // in the query-checkpoint mechanism; serving live values inside an + // otherwise historical response would produce a non-deterministic view. + historical bool + applyDuration metric.Int64Histogram } @@ -185,6 +192,7 @@ func (ctrl *DefaultController) WithStores(store *dal.Store, readStore *readstore clone := *ctrl clone.store = store clone.readStore = readStore + clone.historical = true return &clone } @@ -547,6 +555,18 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st } } + // Historical (checkpoint-scoped) reads intentionally return zero + // usage counters: the usagestore is a projection of the LIVE audit + // chain and does not participate in query-checkpoint machinery, so + // serving live counters alongside checkpointed boundary values would + // produce a non-deterministic response for the same checkpoint id. + // This is the same trade-off that keeps read-index rebuild scoped to + // live state — future work can add checkpointed usage projections if + // clients need them. + if ctrl.historical { + return &stats, nil + } + // Projected counters from the usagebuilder side-store. All reads go // against a single Pebble snapshot so a concurrent usagebuilder commit // cannot land a partial view between them. Missing keys read as 0. @@ -1362,8 +1382,20 @@ func (ctrl *DefaultController) GetNumscript(ctx context.Context, ledger, name st // subsystem asynchronously from the audit chain: a template that was just // invoked may not yet be reflected here for up to one usagebuilder tick // (100 ms). Returns a zero-valued TemplateUsage (never nil) when the -// template has never been invoked. -func (ctrl *DefaultController) GetTemplateUsage(_ context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { +// template has never been invoked on an existing ledger. +// +// Ledger existence is validated first so an unknown or soft-deleted ledger +// surfaces a NotFound business error rather than a zero-valued 200 — the +// same contract as GetLedgerStats / GetNumscript. +func (ctrl *DefaultController) GetTemplateUsage(ctx context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + if _, err := query.GetLedgerByName(ctx, ctrl.store, ledger); err != nil { + if errors.Is(err, domain.ErrNotFound) { + return nil, commonpb.NewNotFoundError("ledger %s not found", ledger) + } + + return nil, err + } + usage, err := ctrl.usageStore.GetTemplateUsage(ledger, name) if err != nil { return nil, fmt.Errorf("reading template usage %q/%q: %w", ledger, name, err) diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 632f2b69f2..6de8825a0b 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -260,16 +260,15 @@ func (b *Builder) loop(ctx context.Context) { case <-ticker.C: } - // Fast path: skip Pebble iterator when nothing new has landed. The - // LastSequence atomic tracks the last LOG sequence — a strict lower - // bound on the last audit sequence (audit entries are written in - // the same batch as their logs). Comparing against our audit cursor - // is conservative: we may wake up spuriously if the last batch had - // only audit updates (rare), but we never miss real work. - if cached := b.notifications.LastSequence.Load(); cached != 0 && cached <= cursor { - continue - } - + // No fast-path atomic gate here: `signal.Notifications.LastSequence` + // tracks the last LOG sequence, whereas our cursor tracks the last + // AUDIT sequence. The two counters advance together in the common + // case but a failed proposal advances the audit chain without + // producing a log — comparing them directly would leave audit + // entries un-projected. processAuditEntries opens a cursor at + // `lastProcessedAuditSeq + 1` and immediately hits EOF when nothing + // new has landed, so the pebble-side cost of a spurious wake-up is + // negligible (one iterator open + close, no snapshot pin). if cursor, err = b.processAuditEntries(ctx, cursor, time.Time{}); err != nil { b.logger.Errorf("Error processing audit entries: %v", err) } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 5cf56c8c7c..3b917f4e63 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -34,19 +34,35 @@ type templateDelta struct { type counterDelta = int64 // batchState holds the in-flight aggregation for one batch: per-ledger -// counter deltas + per-template usage deltas. Reset by newBatchState. +// counter deltas, per-template usage deltas, and the set of ledger names +// dropped by DeleteLedgerOrder entries in this batch. Reset by newBatchState. type batchState struct { - counters map[string]map[byte]counterDelta - templates map[templateKey]templateDelta + counters map[string]map[byte]counterDelta + templates map[templateKey]templateDelta + deletedLedgers map[string]struct{} } func newBatchState() *batchState { return &batchState{ - counters: make(map[string]map[byte]counterDelta), - templates: make(map[templateKey]templateDelta), + counters: make(map[string]map[byte]counterDelta), + templates: make(map[templateKey]templateDelta), + deletedLedgers: make(map[string]struct{}), } } +// markLedgerDeleted flags a ledger as dropped by this batch. commitBatch +// range-deletes every counter / template row for the ledger AFTER the +// counter / template mutations are staged — the final ordering is +// (writes → range delete → cursor advance), inside a single Pebble batch, +// so the range delete wipes both pre-existing rows and any writes the same +// batch may have added just above for the same ledger. This matches the +// FSM's own DeleteLedger cascade (which purges the ledger's Pebble rows +// unconditionally, regardless of whether earlier orders in the same +// proposal updated them). +func (s *batchState) markLedgerDeleted(ledger string) { + s.deletedLedgers[ledger] = struct{}{} +} + // addCounter accumulates a delta on the (ledger, counterID) slot. func (s *batchState) addCounter(ledger string, counterID byte, delta counterDelta) { inner, ok := s.counters[ledger] @@ -82,7 +98,7 @@ func timestampGreater(a, b *commonpb.Timestamp) bool { // empty reports whether the batch has no writes queued. func (s *batchState) empty() bool { - return len(s.counters) == 0 && len(s.templates) == 0 + return len(s.counters) == 0 && len(s.templates) == 0 && len(s.deletedLedgers) == 0 } // RebuildAll replays every audit entry from sequence 0, materialising the @@ -251,8 +267,9 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli // dispatchOrder inspects a raw Order and accumulates counter / template // deltas into the batch state. Fetches the produced log when the resolved -// posting count is required (revert txs, script-backed create txs). entry -// carries the per-audit-entry dedup scratchpad — see applyVolumeAnnotations. +// posting count is required (revert txs, script-backed create txs, mirror +// ingests). entry carries the per-audit-entry dedup scratchpad — see +// applyVolumeAnnotations. func (b *Builder) dispatchOrder( ctx context.Context, handle dal.PebbleGetter, @@ -273,10 +290,28 @@ func (b *Builder) dispatchOrder( return nil } + // DeleteLedger orders MUST be projected — otherwise the usagestore + // keeps stale rows for the dropped ledger until an operator runs + // rebuild-usage. Same story as the readstore's DeleteLedgerIndexes. + if scoped.GetDeleteLedger() != nil { + state.markLedgerDeleted(ledger) + + return nil + } + + // Mirror ingests produce Created/Reverted transaction logs the same + // shape as the direct write path, so posting / revert / volume / + // ephemeral counters all apply. Numscript / reference metadata is + // absent on the mirror wire — no CounterReference / + // CounterNumscriptExecution / template usage contribution. + if mirror := scoped.GetMirrorIngest(); mirror != nil { + return b.dispatchMirrorIngest(ctx, handle, ledger, mirror.GetEntry(), logSeq, state, entry) + } + apply := scoped.GetApply() if apply == nil { - // Non-apply orders (create/delete/promote/mirror-ingest) are not - // event-counted in this MVP. + // CreateLedger / PromoteLedger — no state deltas that concern + // usage counters. return nil } @@ -290,6 +325,50 @@ func (b *Builder) dispatchOrder( return nil } +// dispatchMirrorIngest projects a single MirrorLogEntry — the mirror worker +// replays these on the destination ledger, producing the same CreatedTx / +// RevertedTx logs the direct write path emits. We contribute the +// downstream-observable counters (postings, reverts, volumes, ephemeral) but +// skip client-driven metadata (reference / numscript) since it doesn't +// travel across the mirror wire. +func (b *Builder) dispatchMirrorIngest( + ctx context.Context, + handle dal.PebbleGetter, + ledger string, + mle *raftcmdpb.MirrorLogEntry, + logSeq uint64, + state *batchState, + entry *entryVolumeState, +) error { + if mle == nil { + return nil + } + + switch mle.GetData().(type) { + case *raftcmdpb.MirrorLogEntry_CreatedTransaction: + ann, err := b.readLog(ctx, handle, logSeq) + if err != nil { + return err + } + if ann.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) + } + applyVolumeAnnotations(ledger, ann, state, entry) + case *raftcmdpb.MirrorLogEntry_RevertedTransaction: + state.addCounter(ledger, usagestore.CounterRevert, 1) + ann, err := b.readLog(ctx, handle, logSeq) + if err != nil { + return err + } + if ann.postings > 0 { + state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) + } + applyVolumeAnnotations(ledger, ann, state, entry) + } + + return nil +} + // entryVolumeState is the per-audit-entry deduplication scratchpad. Each set // records the (account, asset) tuples already applied to their respective // counter for the current audit entry. VolumeCount, CounterEphemeralEvicted @@ -393,7 +472,17 @@ func (b *Builder) dispatchCreateTransaction( } if ref := order.GetNumscriptReference(); ref != nil { - state.addTemplateUsage(ledger, ref.GetName(), order.GetTimestamp()) + // Prefer the order's client-supplied timestamp so template usage + // tracks the wall clock the client cares about; when omitted, fall + // back to the effective timestamp the FSM stamped on the produced + // log (either the client value or the proposal date resolved by + // processor_transaction.go). Either way we end up with a + // deterministic non-nil timestamp on every replay. + ts := order.GetTimestamp() + if ts == nil { + ts = ann.txTimestamp + } + state.addTemplateUsage(ledger, ref.GetName(), ts) } return nil @@ -427,14 +516,18 @@ func (b *Builder) dispatchRevertTransaction( } // logVolumeAnnotations bundles the three disjoint TouchedVolume lists that -// LedgerLog carries plus the resolved posting count. The lists are kept as -// slices (not lengths) because the counter dispatch needs per-tuple identity -// for batch-scoped deduplication — see the applyVolumeDelta docstring. +// LedgerLog carries plus the resolved posting count and the transaction +// timestamp. The lists are kept as slices (not lengths) because the counter +// dispatch needs per-tuple identity for batch-scoped deduplication — see +// applyVolumeAnnotations. txTimestamp is the effective timestamp the FSM +// stamped on the transaction (client-provided or falling back to the +// proposal date). Nil for non-transaction logs. type logVolumeAnnotations struct { - postings int - purged []*commonpb.TouchedVolume // len — draining only - newKept []*commonpb.TouchedVolume // new + kept - ephemeral []*commonpb.TouchedVolume // new + purged (pure ephemeral) + postings int + purged []*commonpb.TouchedVolume // len — draining only + newKept []*commonpb.TouchedVolume // new + kept + ephemeral []*commonpb.TouchedVolume // new + purged (pure ephemeral) + txTimestamp *commonpb.Timestamp // Transaction.Timestamp on Created/Reverted logs } // readLog fetches the log at logSeq and returns its posting count plus the @@ -478,9 +571,13 @@ func (b *Builder) readLog(ctx context.Context, handle dal.PebbleGetter, logSeq u switch p := ledgerLog.GetData().GetPayload().(type) { case *commonpb.LedgerLogPayload_CreatedTransaction: - result.postings = len(p.CreatedTransaction.GetTransaction().GetPostings()) + tx := p.CreatedTransaction.GetTransaction() + result.postings = len(tx.GetPostings()) + result.txTimestamp = tx.GetTimestamp() case *commonpb.LedgerLogPayload_RevertedTransaction: - result.postings = len(p.RevertedTransaction.GetRevertTransaction().GetPostings()) + tx := p.RevertedTransaction.GetRevertTransaction() + result.postings = len(tx.GetPostings()) + result.txTimestamp = tx.GetTimestamp() } return result, nil @@ -531,6 +628,19 @@ func (b *Builder) commitBatch(state *batchState, cursor uint64) error { } } + // Ledger deletions come last inside the batch — the DeleteRange scoped + // to `[PrefixTemplate/Counter][ledger 64B]…` wipes every counter / + // template row for the deleted ledger, including any writes staged + // above by earlier orders in the same audit entry. Matches the FSM's + // DeleteLedger cascade semantics: post-commit, no trace remains. + for ledger := range state.deletedLedgers { + if err := usagestore.DeleteLedger(batch, ledger); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("dropping usage rows for deleted ledger %q: %w", ledger, err) + } + } + if err := b.usageStore.WriteProgress(batch, cursor); err != nil { _ = batch.Cancel() From 115d5ea8f622889cb642c0465887dd0c152121a1 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 3 Jul 2026 11:24:16 +0200 Subject: [PATCH 09/35] refactor(EN-1334): port usagebuilder onto shared tailworker skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream EN-1427 landed a shared tail-worker skeleton (loop, gauges, Uint64Cursor) that the audit indexer already uses. Port the usagebuilder onto the same primitives — the hand-rolled loop, the four bespoke gauges and the `LastSequence`-vs-audit-cursor comparison it kept alive can all go away. ## Loop skeleton `builder.loop()` — read cursor, seed pebbleLast, catch-up slices, 100ms ticker + LogCommitted select — is replaced by: tailworker.New(tailworker.Config{ Name: "usage-builder", Ticker: TickInterval, Wake: notifications.LogCommitted.C(), Boot: b.boot, Tick: b.tick, }) `boot` handles cursor init + initial catch-up with the 5-second slice budget; `tick` samples the audit head and drains one processAuditEntries pass. `Wake`-vs-ticker selection, ctx cancellation, panic hygiene and Stop semantics are all inherited from the shared driver. ## Gauges Four bespoke gauges collapse to three via `tailworker.RegisterTailGauges`, matching the audit indexer's namespace convention: usage.builder.last_processed_audit_sequence → last_indexed_sequence usage.builder.pebble_last_audit_sequence → audit_last_sequence usage.builder.lag → (unchanged) usage.builder.entries_processed_total → dropped `entries_processed_total` was a nice-to-have with no operational consumer — the audit indexer doesn't expose it either. Dropped for namespace consistency; can be reintroduced later behind the same helper if a dashboard actually needs it. ## Audit-head sampling The old loop resampled `notifications.LastSequence` (a LOG sequence) inside processAuditEntries — the earlier PR review flagged this as mixing incompatible sequence counters. The new `sampleAuditHead` opens a short-lived read handle, calls `query.ReadLastAuditSequence`, closes. Called from both `boot` (once) and `tick` (every interval). Correct domain, no more atomic-then-hope-it-lines-up. ## metrics.libsonnet + docs Dashboard registry drops the two renamed gauges and the deleted counter. `docs/technical/architecture/subsystems/usage/{counters,usagebuilder}.md` call out that the progress gauges now come from `tailworker.RegisterTailGauges` for cross-subsystem consistency. --- .../architecture/subsystems/usage/counters.md | 7 +- .../subsystems/usage/usagebuilder.md | 4 +- internal/application/usagebuilder/builder.go | 233 ++++++++---------- .../application/usagebuilder/process_audit.go | 11 - .../jsonnet/lib/metrics.libsonnet | 5 +- 5 files changed, 107 insertions(+), 153 deletions(-) diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md index 1681d2bd48..0a1bd4c54b 100644 --- a/docs/technical/architecture/subsystems/usage/counters.md +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -125,8 +125,9 @@ Registered in `misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet`: | Metric | Description | |--------|-------------| -| `usage.builder.last_processed_audit_sequence` | Highest audit sequence the builder has committed for this replica. | -| `usage.builder.pebble_last_audit_sequence` | Highest audit sequence present in Pebble on this replica. | +| `usage.builder.last_indexed_sequence` | Highest audit sequence the builder has committed for this replica. | +| `usage.builder.audit_last_sequence` | Highest audit sequence present in Pebble on this replica. | | `usage.builder.lag` | Difference between the two (indicator of the eventual-consistency window). | -| `usage.builder.entries_processed_total` | Cumulative audit entries consumed since process start. | | `usagestore.level.bytes` / `memtable.bytes` / `cache.hits` / `cache.misses` | Pebble-internal metrics for the usagestore instance (parallel to the readindex namespace). | + +The three progress gauges are registered through `tailworker.RegisterTailGauges` — the same helper the audit indexer uses — so the naming pattern (`{ns}.last_indexed_sequence`, `{ns}.{source}_last_sequence`, `{ns}.lag`) stays consistent across every tail-worker subsystem. diff --git a/docs/technical/architecture/subsystems/usage/usagebuilder.md b/docs/technical/architecture/subsystems/usage/usagebuilder.md index 7961f42e02..b7bb5d654a 100644 --- a/docs/technical/architecture/subsystems/usage/usagebuilder.md +++ b/docs/technical/architecture/subsystems/usage/usagebuilder.md @@ -129,9 +129,11 @@ The audit chain remains the source of truth in both cases. | Concern | Mechanism | File | |---------|-----------|------| -| Wake-up | Dedicated `name:"usage"` `Notifications` from the FSM FanOut + 100 ms ticker | `builder.go` | +| Loop skeleton | `tailworker.TailWorker` — shared boot/tick/wake driver used by every tail-worker subsystem (audit indexer, usagebuilder, …) | `internal/pkg/tailworker/tailworker.go` | +| Wake-up | Dedicated `name:"usage"` `Notifications` from the FSM FanOut fed into the tailworker's `Wake` channel + `TickInterval` fallback | `builder.go` | | Source | Audit chain (`ReadAuditEntries` + `ReadAuditItems`), not the log stream — needed for Numscript template ref survival | `process_audit.go` | | Atomicity | `WriteProgress` shares the same Pebble batch as counter / template mutations | `process_audit.go`, `usagestore/store.go` | | Read consistency | Multi-counter reads via `usagestore.NewSnapshot()` | `usagestore/snapshot.go`, `ctrl/controller_default.go` | | Isolation | Dedicated Pebble instance at `/usage/`, own comparer, WAL disabled | `usagestore/{store,comparer,keys}.go` | +| Metrics | `tailworker.RegisterTailGauges` — 3 shared gauges (`last_indexed_sequence`, `audit_last_sequence`, `lag`) | `builder.go`, `internal/pkg/tailworker/gauges.go` | | Rebuild | `ledgerctl store rebuild-usage` — drop directory + replay | `cmd/ledgerctl/store/rebuild_usage.go` | diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 6de8825a0b..3a97c5526d 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -16,11 +16,12 @@ // // Runs on every node; each replica maintains its own cursor (last consumed // audit sequence). Eventually consistent with the FSM: reads may lag by up -// to one tick interval (100 ms) plus batch drain time. +// to one tick interval plus batch drain time. package usagebuilder import ( "context" + "fmt" "sync/atomic" "time" @@ -29,7 +30,7 @@ import ( logging "github.com/formancehq/go-libs/v5/pkg/observe/log" "github.com/formancehq/ledger/v3/internal/pkg/signal" - "github.com/formancehq/ledger/v3/internal/pkg/worker" + "github.com/formancehq/ledger/v3/internal/pkg/tailworker" "github.com/formancehq/ledger/v3/internal/query" "github.com/formancehq/ledger/v3/internal/storage/dal" "github.com/formancehq/ledger/v3/internal/storage/usagestore" @@ -38,6 +39,16 @@ import ( // DefaultBatchSize is the default number of audit entries per Pebble batch commit. const DefaultBatchSize = 200 +// TickInterval is the steady-state polling interval. Same rationale as the +// audit indexer: the audit sequence advances on every proposal (including +// failures that emit no log), so a ticker is what guarantees pickup. +const TickInterval = 100 * time.Millisecond + +// catchUpBudget bounds how long a single processAuditEntries invocation +// holds a Pebble snapshot during boot-time catch-up. Between slices the +// snapshot is released so compactions can proceed on long-history stores. +const catchUpBudget = 5 * time.Second + // Builder tails the FSM audit chain and populates the usagestore projections. // Runs as a background goroutine on all nodes (not leader-only). Progress is // stored in the usagestore itself under [0xFE][0x01]. @@ -47,14 +58,19 @@ type Builder struct { notifications *signal.Notifications logger logging.Logger meter metric.Meter - w worker.Worker batchSize int + // lastProcessedAuditSeq mirrors usagestore.ReadProgress() and is + // updated on every successful commit — the atomic hint lets external + // readers (metrics, tests) avoid a Pebble Get. lastProcessedAuditSeq atomic.Uint64 - pebbleLastAuditSeq atomic.Uint64 - entriesProcessed atomic.Uint64 - metricsRegistration metric.Registration + // pebbleLastAuditSeq is the highest audit sequence in the main store, + // resampled on each tick for the lag gauge. + pebbleLastAuditSeq atomic.Uint64 + + tw *tailworker.TailWorker + reg metric.Registration } // NewBuilder wires the usagebuilder subsystem. Notifications is injected via @@ -82,122 +98,72 @@ func NewBuilder( } } -// Start begins the background loop and registers OTEL metrics. +// Start launches the background tail loop and registers OTEL gauges. func (b *Builder) Start() { - if reg, err := b.registerMetrics(); err == nil { - b.metricsRegistration = reg + if reg, err := tailworker.RegisterTailGauges( + b.meter, "usage.builder", "audit", &b.lastProcessedAuditSeq, &b.pebbleLastAuditSeq, + ); err == nil { + b.reg = reg + } + + // Wake on FSM commit signals when available. `notifications` is nil + // only in the offline rebuild path (RebuildAll), which does not go + // through Start — the guard is defensive. + var wake <-chan struct{} + if b.notifications != nil { + wake = b.notifications.LogCommitted.C() } - b.w = worker.New() - b.w.RunCtx(b.loop) + b.tw = tailworker.New(tailworker.Config{ + Name: "usage-builder", + Logger: b.logger, + Ticker: TickInterval, + Wake: wake, + Boot: b.boot, + Tick: b.tick, + }) + b.tw.Start() } -// Stop gracefully stops the background loop and unregisters OTEL metrics. +// Stop halts the tail loop and unregisters metrics. func (b *Builder) Stop() { - b.w.Stop() - - if b.metricsRegistration != nil { - _ = b.metricsRegistration.Unregister() + if b.tw != nil { + b.tw.Stop() + } + if b.reg != nil { + _ = b.reg.Unregister() } } -// LastProcessedAuditSequence returns the last audit sequence consumed (from -// the atomic cache — same value as usagestore.ReadProgress but without a -// Pebble Get). +// LastProcessedAuditSequence returns the last audit sequence consumed +// (atomic hint — same value as usagestore.ReadProgress but without a +// Pebble Get). Exposed for tests and health checks. func (b *Builder) LastProcessedAuditSequence() uint64 { return b.lastProcessedAuditSeq.Load() } -// PebbleLastAuditSequence returns the last known Pebble audit sequence (from -// the atomic cache). +// PebbleLastAuditSequence returns the last known main-store audit sequence +// (atomic hint refreshed each tick). func (b *Builder) PebbleLastAuditSequence() uint64 { return b.pebbleLastAuditSeq.Load() } -// registerMetrics registers observable gauges for the usagebuilder. -func (b *Builder) registerMetrics() (metric.Registration, error) { - lastProcessedGauge, err := b.meter.Int64ObservableGauge( - "usage.builder.last_processed_audit_sequence", - metric.WithDescription("Last audit sequence consumed by the usagebuilder"), - ) - if err != nil { - return nil, err - } - - pebbleLastGauge, err := b.meter.Int64ObservableGauge( - "usage.builder.pebble_last_audit_sequence", - metric.WithDescription("Last audit sequence in Pebble"), - ) - if err != nil { - return nil, err - } - - lagGauge, err := b.meter.Int64ObservableGauge( - "usage.builder.lag", - metric.WithDescription("Number of audit entries the usagebuilder is behind Pebble"), - ) - if err != nil { - return nil, err - } - - entriesProcessedGauge, err := b.meter.Int64ObservableGauge( - "usage.builder.entries_processed_total", - metric.WithDescription("Total number of audit entries consumed since process start"), - ) - if err != nil { - return nil, err - } - - return b.meter.RegisterCallback( - func(_ context.Context, o metric.Observer) error { - processed := int64(b.lastProcessedAuditSeq.Load()) - pebbleLast := int64(b.pebbleLastAuditSeq.Load()) - - lag := max(pebbleLast-processed, 0) - - o.ObserveInt64(lastProcessedGauge, processed) - o.ObserveInt64(pebbleLastGauge, pebbleLast) - o.ObserveInt64(lagGauge, lag) - o.ObserveInt64(entriesProcessedGauge, int64(b.entriesProcessed.Load())) - - return nil - }, - lastProcessedGauge, - pebbleLastGauge, - lagGauge, - entriesProcessedGauge, - ) -} - -// loop is the main goroutine driven by Start(). Reads cursor from the usage -// store, catches up on any pending audit entries, then tails via a 100 ms -// ticker plus the LogCommitted notification (fires whenever the FSM -// advances, which is also when the audit chain advances). -func (b *Builder) loop(ctx context.Context) { +// boot runs once before the tail loop: seed both atomics from the persisted +// state and drain the reachable backlog with a bigger batch size so the +// steady-state loop starts already caught up. A cursor-read error aborts the +// loop (returned to tailworker, which logs and stops); a catch-up error is +// logged and swallowed so steady-state indexing still starts. +func (b *Builder) boot(ctx context.Context) error { cursor, err := b.usageStore.ReadProgress() if err != nil { - b.logger.Errorf("Failed to read usage progress: %v", err) - - return + return fmt.Errorf("reading usage progress: %w", err) } b.lastProcessedAuditSeq.Store(cursor) - // Seed pebble last audit sequence. Handle closed immediately to release - // the RLock — keeping it open would deadlock with RestoreCheckpoint - // (write lock) when processAuditEntries takes a new RLock. - var pebbleLast uint64 - if handle, err := b.pebbleStore.NewDirectReadHandle(); err != nil { - b.logger.Errorf("Failed to create read handle: %v", err) - - return - } else { - if v, err := query.ReadLastAuditSequence(handle); err == nil { - pebbleLast = v - b.pebbleLastAuditSeq.Store(v) - } - - _ = handle.Close() + pebbleLast, sampleErr := b.sampleAuditHead() + if sampleErr == nil { + b.pebbleLastAuditSeq.Store(pebbleLast) } b.logger.WithFields(map[string]any{ @@ -206,28 +172,25 @@ func (b *Builder) loop(ctx context.Context) { "gap": int64(pebbleLast) - int64(cursor), }).Infof("Usage builder started") - // Initial catch-up: time-bounded iterations to release the Pebble - // snapshot between passes (same rationale as indexbuilder catchUpBudget). - const catchUpBudget = 5 * time.Second - + // Initial catch-up — time-bounded slices so the Pebble snapshot is + // released between passes. Larger batch size so bootstrap commits are + // coalesced. prevCursor := cursor savedBatchSize := b.batchSize b.batchSize = max(b.batchSize, 2_000) + defer func() { b.batchSize = savedBatchSize }() for { - select { - case <-ctx.Done(): - b.batchSize = savedBatchSize - - return - default: + if err := ctx.Err(); err != nil { + return err } before := cursor deadline := time.Now().Add(catchUpBudget) - if cursor, err = b.processAuditEntries(ctx, cursor, deadline); err != nil { - b.logger.Errorf("Error during initial catch-up: %v", err) + cursor, err = b.processAuditEntries(ctx, cursor, deadline) + if err != nil { + b.logger.Errorf("initial catch-up: %v", err) break } @@ -237,8 +200,6 @@ func (b *Builder) loop(ctx context.Context) { } } - b.batchSize = savedBatchSize - if cursor > prevCursor { b.logger.WithFields(map[string]any{ "from": prevCursor, @@ -247,30 +208,32 @@ func (b *Builder) loop(ctx context.Context) { }).Infof("Initial catch-up complete") } - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() + return nil +} + +// tick runs one steady-state iteration: refresh the audit-head gauge, then +// drain any pending audit entries from the persisted cursor forward. +func (b *Builder) tick(ctx context.Context) error { + if last, err := b.sampleAuditHead(); err == nil { + b.pebbleLastAuditSeq.Store(last) + } - for { - select { - case <-ctx.Done(): - b.logger.Infof("Usage builder stopped") + cursor := b.lastProcessedAuditSeq.Load() + _, err := b.processAuditEntries(ctx, cursor, time.Time{}) - return - case <-b.notifications.LogCommitted.C(): - case <-ticker.C: - } + return err +} - // No fast-path atomic gate here: `signal.Notifications.LastSequence` - // tracks the last LOG sequence, whereas our cursor tracks the last - // AUDIT sequence. The two counters advance together in the common - // case but a failed proposal advances the audit chain without - // producing a log — comparing them directly would leave audit - // entries un-projected. processAuditEntries opens a cursor at - // `lastProcessedAuditSeq + 1` and immediately hits EOF when nothing - // new has landed, so the pebble-side cost of a spurious wake-up is - // negligible (one iterator open + close, no snapshot pin). - if cursor, err = b.processAuditEntries(ctx, cursor, time.Time{}); err != nil { - b.logger.Errorf("Error processing audit entries: %v", err) - } +// sampleAuditHead opens a short-lived read handle to read the current audit +// head. The handle is closed immediately so RestoreCheckpoint's write lock +// is not blocked during idle ticks. +func (b *Builder) sampleAuditHead() (uint64, error) { + handle, err := b.pebbleStore.NewDirectReadHandle() + if err != nil { + return 0, err } + + defer func() { _ = handle.Close() }() + + return query.ReadLastAuditSequence(handle) } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 3b917f4e63..00bf2a77a7 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -230,17 +230,6 @@ func (b *Builder) processAuditEntries(ctx context.Context, cursor uint64, deadli cursor = lastAuditSeq b.lastProcessedAuditSeq.Store(cursor) - b.entriesProcessed.Add(uint64(batchCount)) - - // Sample Pebble last audit sequence from the FSM notification cache - // when available. Notifications is nil in offline rebuild - // (`ledgerctl store rebuild-usage`) where no FSM is running — the - // atomic is skipped in that path. - if b.notifications != nil { - if cached := b.notifications.LastSequence.Load(); cached > 0 { - b.pebbleLastAuditSeq.Store(cached) - } - } // Periodic progress logging for long catch-up runs. if now := time.Now(); now.Sub(lastProgressLog) >= 10*time.Second { diff --git a/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet b/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet index 0633c83aa4..da0b46c9e8 100644 --- a/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet +++ b/misc/devenv/monitoring-dashboards/jsonnet/lib/metrics.libsonnet @@ -72,10 +72,9 @@ // usage.builder — internal/application/usagebuilder/builder.go usage_builder:: { - last_processed_audit_sequence: 'usage.builder.last_processed_audit_sequence', - pebble_last_audit_sequence: 'usage.builder.pebble_last_audit_sequence', + last_indexed_sequence: 'usage.builder.last_indexed_sequence', + audit_last_sequence: 'usage.builder.audit_last_sequence', lag: 'usage.builder.lag', - entries_processed_total: 'usage.builder.entries_processed_total', }, // numscript — internal/domain/processing/numscript/cache.go From e64a73e8c9b19b7b1e03f9f0c237a428cd0e884c Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 6 Jul 2026 11:04:52 +0200 Subject: [PATCH 10/35] fix(EN-1334): 4 legit findings from PR round 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rebuild-usage opened dataDir as the Pebble path instead of dataDir/live, diverging from rebuild-indexes / rebuild-audit-index. It would open (or fail to open) the wrong directory. Aligned with the sibling commands. - ensureDisjointDirs missed the case where --usage-dir sits inside (or is a parent of) /live. The current guards only compared usage-dir against data-dir directly, so a path like /live/whatever silently passed then got RemoveAll'd. Added three explicit rejections around the live subdirectory and a unit test covering every allowed and every rejected shape. - Delete + same-name recreate within a single audit batch previously wiped post-recreate counters: commitBatch ordered writes first, then DeleteRange, so the post-recreate Puts were rolled back by the cascade. markLedgerDeleted now also drops the ledger's accumulator entries in batchState (pre-delete deltas belong to the old incarnation), and commitBatch enqueues the DeleteRange cascade first so later Puts inside the same Pebble batch shadow it — matching FSM cascade semantics without losing legitimate same-batch writes. - MirrorCreatedTransaction carries a reference field (proto field 5) that the mirror dispatcher was silently ignoring, so references created via the v2→v3 mirror never contributed to CounterReference. The dispatcher now bumps the reference counter when the mirrored transaction carries a non-empty reference. Numscript template usage stays skipped for mirrored logs — v2 sources do not surface template metadata. --- cmd/ledgerctl/store/rebuild_usage.go | 35 ++++++--- cmd/ledgerctl/store/rebuild_usage_test.go | 72 +++++++++++++++++++ .../application/usagebuilder/process_audit.go | 72 ++++++++++++------- 3 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 cmd/ledgerctl/store/rebuild_usage_test.go diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index 02d0f049a4..69c4014be5 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -18,12 +18,19 @@ import ( "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) -// ensureDisjointDirs rejects overlapping --data-dir / --usage-dir values — -// the rebuild command RemoveAlls the usage dir before opening the data dir, -// so an operator who passes the same path (or a parent) would wipe the live -// Pebble store. Comparison is done on cleaned absolute paths so relative -// forms, trailing separators and symbolic paths all normalise before the -// prefix check. +// ensureDisjointDirs rejects any --usage-dir value that would overlap the +// primary Pebble store — the rebuild command RemoveAlls the usage dir before +// re-opening the data dir, so a colliding path silently wipes production +// data. +// +// The four rejected shapes on cleaned absolute paths: +// - usageDir == dataDir (obvious: wipes the whole data root) +// - usageDir is a parent of dataDir (wipes the whole data root) +// - usageDir == /live (wipes Pebble's actual live directory) +// - usageDir is a parent or child of /live (wipes Pebble too) +// +// The documented default is `/usage`, which is a sibling of +// `/live` and therefore safe. func ensureDisjointDirs(dataDir, usageDir string) error { absData, err := filepath.Abs(dataDir) if err != nil { @@ -33,6 +40,7 @@ func ensureDisjointDirs(dataDir, usageDir string) error { if err != nil { return fmt.Errorf("resolving --usage-dir: %w", err) } + absLive := filepath.Join(absData, "live") if absData == absUsage { return fmt.Errorf("--usage-dir (%s) must not equal --data-dir — running this command would delete the primary Pebble store", absUsage) @@ -40,6 +48,15 @@ func ensureDisjointDirs(dataDir, usageDir string) error { if strings.HasPrefix(absData+string(filepath.Separator), absUsage+string(filepath.Separator)) { return fmt.Errorf("--usage-dir (%s) must not be a parent of --data-dir (%s) — running this command would delete the primary Pebble store", absUsage, absData) } + if absUsage == absLive { + return fmt.Errorf("--usage-dir (%s) must not equal the primary Pebble live directory — running this command would delete it", absUsage) + } + if strings.HasPrefix(absLive+string(filepath.Separator), absUsage+string(filepath.Separator)) { + return fmt.Errorf("--usage-dir (%s) must not be a parent of the primary Pebble live directory (%s) — running this command would delete it", absUsage, absLive) + } + if strings.HasPrefix(absUsage+string(filepath.Separator), absLive+string(filepath.Separator)) { + return fmt.Errorf("--usage-dir (%s) must not live inside the primary Pebble directory (%s) — running this command would delete Pebble state", absUsage, absLive) + } return nil } @@ -108,10 +125,12 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { spinner.Success("Usage store dropped at " + usageDir) - // Open primary Pebble read-only — same as rebuild-indexes. + // Open primary Pebble read-only — same as rebuild-indexes / + // rebuild-audit-index (the live SSTs are at /live, not + // dataDir itself). spinner, _ = pterm.DefaultSpinner.Start("Opening Pebble store (read-only)...") - pebbleStore, err := dal.OpenReadOnly(dataDir, logger) + pebbleStore, err := dal.OpenReadOnly(filepath.Join(dataDir, "live"), logger) if err != nil { spinner.Fail("Failed to open Pebble store") diff --git a/cmd/ledgerctl/store/rebuild_usage_test.go b/cmd/ledgerctl/store/rebuild_usage_test.go new file mode 100644 index 0000000000..6cc34c5253 --- /dev/null +++ b/cmd/ledgerctl/store/rebuild_usage_test.go @@ -0,0 +1,72 @@ +package store + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestEnsureDisjointDirs exercises the guards that prevent `--usage-dir` +// from silently wiping the primary Pebble store. The command RemoveAlls the +// usage dir before opening data, so any overlap is destructive. +func TestEnsureDisjointDirs(t *testing.T) { + t.Parallel() + + root := t.TempDir() + dataDir := filepath.Join(root, "data") + liveDir := filepath.Join(dataDir, "live") + + tests := []struct { + name string + usage string + wantErr string + }{ + { + name: "sibling under data-dir (default)", + usage: filepath.Join(dataDir, "usage"), + }, + { + name: "fully separate root", + usage: filepath.Join(root, "elsewhere", "usage"), + }, + { + name: "equal to data-dir", + usage: dataDir, + wantErr: "must not equal --data-dir", + }, + { + name: "parent of data-dir", + usage: root, + wantErr: "must not be a parent of --data-dir", + }, + { + name: "equal to /live", + usage: liveDir, + wantErr: "must not equal the primary Pebble live directory", + }, + { + name: "parent of /live but distinct from data-dir", + usage: filepath.Join(dataDir), + wantErr: "must not equal --data-dir", + }, + { + name: "inside /live", + usage: filepath.Join(liveDir, "nested"), + wantErr: "must not live inside the primary Pebble directory", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := ensureDisjointDirs(dataDir, tc.usage) + if tc.wantErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.wantErr) + } + }) + } +} diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 00bf2a77a7..a4a4dd9123 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -50,17 +50,27 @@ func newBatchState() *batchState { } } -// markLedgerDeleted flags a ledger as dropped by this batch. commitBatch -// range-deletes every counter / template row for the ledger AFTER the -// counter / template mutations are staged — the final ordering is -// (writes → range delete → cursor advance), inside a single Pebble batch, -// so the range delete wipes both pre-existing rows and any writes the same -// batch may have added just above for the same ledger. This matches the -// FSM's own DeleteLedger cascade (which purges the ledger's Pebble rows -// unconditionally, regardless of whether earlier orders in the same -// proposal updated them). +// markLedgerDeleted flags a ledger as dropped by this batch and drops any +// in-batch counter / template deltas already accumulated for it (they are +// for the pre-delete incarnation and must not survive the DeleteLedger). +// +// commitBatch runs the DeleteRange cascade FIRST inside the Pebble batch, +// then stages the counter / template Puts on top — later batch ops shadow +// earlier ones at commit, so if the same audit batch contains a delete +// followed by a same-name recreate + writes, the post-recreate Puts survive +// while every pre-batch row for the old incarnation is wiped. Combined with +// the accumulator reset here, that yields the same net semantics as the +// FSM's own DeleteLedger cascade (which unconditionally purges the ledger's +// Pebble rows regardless of earlier orders in the same proposal). func (s *batchState) markLedgerDeleted(ledger string) { s.deletedLedgers[ledger] = struct{}{} + delete(s.counters, ledger) + + for k := range s.templates { + if k.ledger == ledger { + delete(s.templates, k) + } + } } // addCounter accumulates a delta on the (ledger, counterID) slot. @@ -290,9 +300,11 @@ func (b *Builder) dispatchOrder( // Mirror ingests produce Created/Reverted transaction logs the same // shape as the direct write path, so posting / revert / volume / - // ephemeral counters all apply. Numscript / reference metadata is - // absent on the mirror wire — no CounterReference / - // CounterNumscriptExecution / template usage contribution. + // ephemeral counters all apply. References ARE carried across the + // mirror wire (MirrorCreatedTransaction.reference) and counted the + // same as native creates. Numscript templates are not — v2 sources + // do not carry per-template invocation metadata, so we skip + // CounterNumscriptExecution and template usage for mirrored logs. if mirror := scoped.GetMirrorIngest(); mirror != nil { return b.dispatchMirrorIngest(ctx, handle, ledger, mirror.GetEntry(), logSeq, state, entry) } @@ -333,7 +345,7 @@ func (b *Builder) dispatchMirrorIngest( return nil } - switch mle.GetData().(type) { + switch data := mle.GetData().(type) { case *raftcmdpb.MirrorLogEntry_CreatedTransaction: ann, err := b.readLog(ctx, handle, logSeq) if err != nil { @@ -342,6 +354,9 @@ func (b *Builder) dispatchMirrorIngest( if ann.postings > 0 { state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) } + if data.CreatedTransaction.GetReference() != "" { + state.addCounter(ledger, usagestore.CounterReference, 1) + } applyVolumeAnnotations(ledger, ann, state, entry) case *raftcmdpb.MirrorLogEntry_RevertedTransaction: state.addCounter(ledger, usagestore.CounterRevert, 1) @@ -574,9 +589,27 @@ func (b *Builder) readLog(ctx context.Context, handle dal.PebbleGetter, logSeq u // commitBatch applies the accumulated counter / template deltas to the // usagestore and advances the cursor — all in a single Pebble batch commit. +// +// Ordering inside the batch: DeleteRange cascade FIRST, then counter / +// template Puts. Pebble batches apply operations in enqueue order at commit, +// so any Put on a key inside a DeleteRange range enqueued earlier still lands +// (later ops shadow earlier ones). Combined with markLedgerDeleted clearing +// in-batch counters for the deleted ledger, this yields the correct semantic +// for a delete+recreate sequence within the same audit batch: every +// pre-batch row for the old incarnation is wiped, while any post-recreate +// Puts on the recycled name survive. func (b *Builder) commitBatch(state *batchState, cursor uint64) error { batch := b.usageStore.NewBatch() + // Ledger deletions first — see the function comment. + for ledger := range state.deletedLedgers { + if err := usagestore.DeleteLedger(batch, ledger); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("dropping usage rows for deleted ledger %q: %w", ledger, err) + } + } + // Counter deltas: read-modify-write against the usagestore. Not the // FSM's Pebble — invariant #3 does not apply here. for ledger, counters := range state.counters { @@ -617,19 +650,6 @@ func (b *Builder) commitBatch(state *batchState, cursor uint64) error { } } - // Ledger deletions come last inside the batch — the DeleteRange scoped - // to `[PrefixTemplate/Counter][ledger 64B]…` wipes every counter / - // template row for the deleted ledger, including any writes staged - // above by earlier orders in the same audit entry. Matches the FSM's - // DeleteLedger cascade semantics: post-commit, no trace remains. - for ledger := range state.deletedLedgers { - if err := usagestore.DeleteLedger(batch, ledger); err != nil { - _ = batch.Cancel() - - return fmt.Errorf("dropping usage rows for deleted ledger %q: %w", ledger, err) - } - } - if err := b.usageStore.WriteProgress(batch, cursor); err != nil { _ = batch.Cancel() From 9ad9b147cc1884fa18f46d82a52c52cfa0ee3a3f Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 6 Jul 2026 11:20:50 +0200 Subject: [PATCH 11/35] fix(EN-1334): drop redundant filepath.Join(dataDir) test case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gocritic flagged the single-arg Join, and the case was in fact just an alias for 'equal to data-dir' — dataDir is by definition the direct parent of dataDir/live, so it hits the 'must not equal --data-dir' guard first. --- cmd/ledgerctl/store/rebuild_usage_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmd/ledgerctl/store/rebuild_usage_test.go b/cmd/ledgerctl/store/rebuild_usage_test.go index 6cc34c5253..83638c0d1b 100644 --- a/cmd/ledgerctl/store/rebuild_usage_test.go +++ b/cmd/ledgerctl/store/rebuild_usage_test.go @@ -45,11 +45,6 @@ func TestEnsureDisjointDirs(t *testing.T) { usage: liveDir, wantErr: "must not equal the primary Pebble live directory", }, - { - name: "parent of /live but distinct from data-dir", - usage: filepath.Join(dataDir), - wantErr: "must not equal --data-dir", - }, { name: "inside /live", usage: filepath.Join(liveDir, "nested"), From 4cd037820f3b3466d25b4702e03d5b5d4bf730ed Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 6 Jul 2026 11:38:54 +0200 Subject: [PATCH 12/35] fix(EN-1334): 3 new PR round-3 findings - Draining evictions now bump CounterEphemeralEvicted. The pre-EN-1420 counter tallied every log-level eviction, not just the new+purged same-log tuples routed to EphemeralVolumes. A volume that persisted with a balance and drains to zero shows up in PurgedVolumes; the usagebuilder was skipping the eviction bump for that path, leaving LedgerStats.EphemeralEvictedCount too low on normal drain-to-zero workloads. - ensureDisjointDirs now resolves symlinks before comparing --usage-dir and --data-dir. filepath.Abs on its own leaves symlinked path components intact, so a --usage-dir pointing at a symlink whose target is inside /live silently bypassed every prefix check and got RemoveAll'd. canonicalizeDir walks up to the deepest existing ancestor and EvalSymlinks it, so partly-existing usage paths on a first rebuild also normalise. New test exercises the symlink-alias case explicitly. - usagebuilder.boot now returns a hard error when the persisted cursor is ahead of the audit head. That state is reachable after a restore from an older checkpoint or a wipe of the primary store while the usagestore is preserved: silently resuming would leave counter / template rows for the rolled-back entries visible forever. The operator is directed to `ledgerctl store rebuild-usage`, which drops the usagestore and replays from audit sequence 0. --- cmd/ledgerctl/store/rebuild_usage.go | 53 +++++++++++++++++-- cmd/ledgerctl/store/rebuild_usage_test.go | 23 ++++++++ internal/application/usagebuilder/builder.go | 14 +++++ .../application/usagebuilder/process_audit.go | 6 +++ 4 files changed, 92 insertions(+), 4 deletions(-) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index 69c4014be5..9c44e8e52e 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -1,7 +1,9 @@ package store import ( + "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -18,12 +20,52 @@ import ( "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) +// canonicalizeDir resolves p to its absolute, symlink-free form. If p (or +// any leading ancestor) does not exist yet — typical for --usage-dir on a +// first rebuild — the deepest existing ancestor is resolved and the missing +// tail re-appended, so a symlinked parent still normalises before we do +// prefix comparisons in ensureDisjointDirs. +func canonicalizeDir(p string) (string, error) { + abs, err := filepath.Abs(p) + if err != nil { + return "", fmt.Errorf("resolving absolute path for %q: %w", p, err) + } + + curr := abs + tail := "" + + for { + resolved, err := filepath.EvalSymlinks(curr) + if err == nil { + if tail == "" { + return resolved, nil + } + + return filepath.Join(resolved, tail), nil + } + if !errors.Is(err, fs.ErrNotExist) { + return "", fmt.Errorf("resolving symlinks for %q: %w", curr, err) + } + + parent := filepath.Dir(curr) + if parent == curr { + // Reached the filesystem root without finding an existing + // ancestor. Fall back to the unresolved absolute path. + return abs, nil + } + tail = filepath.Join(filepath.Base(curr), tail) + curr = parent + } +} + // ensureDisjointDirs rejects any --usage-dir value that would overlap the // primary Pebble store — the rebuild command RemoveAlls the usage dir before // re-opening the data dir, so a colliding path silently wipes production // data. // -// The four rejected shapes on cleaned absolute paths: +// The four rejected shapes on canonicalised paths (symlinks resolved so an +// operator cannot bypass the check by pointing --usage-dir at a symlinked +// alias of the primary store): // - usageDir == dataDir (obvious: wipes the whole data root) // - usageDir is a parent of dataDir (wipes the whole data root) // - usageDir == /live (wipes Pebble's actual live directory) @@ -32,15 +74,18 @@ import ( // The documented default is `/usage`, which is a sibling of // `/live` and therefore safe. func ensureDisjointDirs(dataDir, usageDir string) error { - absData, err := filepath.Abs(dataDir) + absData, err := canonicalizeDir(dataDir) if err != nil { return fmt.Errorf("resolving --data-dir: %w", err) } - absUsage, err := filepath.Abs(usageDir) + absUsage, err := canonicalizeDir(usageDir) if err != nil { return fmt.Errorf("resolving --usage-dir: %w", err) } - absLive := filepath.Join(absData, "live") + absLive, err := canonicalizeDir(filepath.Join(absData, "live")) + if err != nil { + return fmt.Errorf("resolving --data-dir/live: %w", err) + } if absData == absUsage { return fmt.Errorf("--usage-dir (%s) must not equal --data-dir — running this command would delete the primary Pebble store", absUsage) diff --git a/cmd/ledgerctl/store/rebuild_usage_test.go b/cmd/ledgerctl/store/rebuild_usage_test.go index 83638c0d1b..71fb640cd1 100644 --- a/cmd/ledgerctl/store/rebuild_usage_test.go +++ b/cmd/ledgerctl/store/rebuild_usage_test.go @@ -1,12 +1,35 @@ package store import ( + "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) +// TestEnsureDisjointDirs_Symlink verifies the guard resolves symlinks — +// filepath.Abs alone would let a symlink pointing at /live slip +// past the prefix check and get RemoveAll'd. +func TestEnsureDisjointDirs_Symlink(t *testing.T) { + t.Parallel() + + root := t.TempDir() + dataDir := filepath.Join(root, "data") + liveDir := filepath.Join(dataDir, "live") + require.NoError(t, os.MkdirAll(liveDir, 0o755)) + + // symlink-to-live: usage-dir is a symbolic link whose target is the + // primary Pebble live directory. Must be rejected even though the + // literal path string differs. + symlink := filepath.Join(root, "usage-symlink") + require.NoError(t, os.Symlink(liveDir, symlink)) + + err := ensureDisjointDirs(dataDir, symlink) + require.Error(t, err) + require.Contains(t, err.Error(), "must not equal the primary Pebble live directory") +} + // TestEnsureDisjointDirs exercises the guards that prevent `--usage-dir` // from silently wiping the primary Pebble store. The command RemoveAlls the // usage dir before opening data, so any overlap is destructive. diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 3a97c5526d..2bfb99a32b 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -163,6 +163,20 @@ func (b *Builder) boot(ctx context.Context) error { pebbleLast, sampleErr := b.sampleAuditHead() if sampleErr == nil { + // Cursor ahead of the audit head means the primary Pebble store + // was restored to an earlier checkpoint (or wiped) while the + // usagestore was kept. Silently resuming would leave stale + // counter / template rows for the rolled-back entries visible + // forever. Fail loud instead — the recovery path is + // `ledgerctl store rebuild-usage`, which drops the usagestore + // and replays from audit sequence 0. + if cursor > pebbleLast { + return fmt.Errorf( + "usage cursor (%d) is ahead of the audit head (%d): the primary store appears to have been restored to an earlier checkpoint — run `ledgerctl store rebuild-usage` to rebuild the usage projections from the current audit chain", + cursor, pebbleLast, + ) + } + b.pebbleLastAuditSeq.Store(pebbleLast) } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index a4a4dd9123..eecf9ca613 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -419,6 +419,11 @@ func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batc state.addCounter(ledger, usagestore.CounterVolume, 1) } + // Draining evictions: a volume that persisted with a non-zero balance + // and now goes back to zero. Both the volume counter (–1, it was + // counted before) and the eviction counter (+1, this is an eviction + // event) contribute. The pre-EN-1420 EphemeralEvictedCount tallied + // every log-level eviction, not just pure ephemeral tuples. for _, v := range ann.purged { k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} if _, ok := entry.seenPurged[k]; ok { @@ -426,6 +431,7 @@ func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batc } entry.seenPurged[k] = struct{}{} state.addCounter(ledger, usagestore.CounterVolume, -1) + state.addCounter(ledger, usagestore.CounterEphemeralEvicted, 1) } for _, v := range ann.ephemeral { From 8b51d433d02b74e7a0d04159e95a90d378e3a0ef Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 6 Jul 2026 11:53:22 +0200 Subject: [PATCH 13/35] docs(EN-1334): document 404 on GetTemplateUsage endpoint Controller validates ledger existence via query.GetLedgerByName and surfaces NotFound for unknown/soft-deleted ledgers; the OpenAPI spec must document that response like every sibling ledger-scoped endpoint. --- openapi.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openapi.yml b/openapi.yml index d935f0ab40..381a02209e 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1451,6 +1451,8 @@ paths: $ref: '#/components/schemas/GetTemplateUsageResponse' '400': $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' '500': From 645c1789b85b97bd15d52a38fd1fe76cb5f5fd3f Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 10 Jul 2026 14:10:01 +0200 Subject: [PATCH 14/35] chore(EN-1334): address NumaryBot review findings - Fix lastUsed timestamp unit in GetNumscriptUsage HTTP handler: Timestamp.Data is Unix microseconds, use the canonical AsTime() (time.UnixMicro) instead of treating it as nanoseconds (was rendering ~1970 dates). - Fix same-batch delete+recreate resurrecting stale usage counters/template usage: GetCounter/GetTemplateUsage read the committed DB, not the pending batch DeleteRange, so use a zero baseline for ledgers deleted in the batch. Add a regression test that fails without the fix. - Document the deliberate exclusion of the usagestore from the checker (peer secondary store, rebuildable from audit; invariant #8 is scoped to the primary store). - Fix stale 'nanoseconds' comment on timestampGreater (Data is microseconds). --- .../subsystems/usage/usagebuilder.md | 4 ++ .../http/handlers_get_numscript_usage.go | 4 +- .../application/usagebuilder/process_audit.go | 41 +++++++++++---- .../usagebuilder/process_audit_test.go | 52 +++++++++++++++++++ 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/docs/technical/architecture/subsystems/usage/usagebuilder.md b/docs/technical/architecture/subsystems/usage/usagebuilder.md index b7bb5d654a..48ab58c0b2 100644 --- a/docs/technical/architecture/subsystems/usage/usagebuilder.md +++ b/docs/technical/architecture/subsystems/usage/usagebuilder.md @@ -125,6 +125,10 @@ The usagestore is not part of Pebble snapshots or backups: it is a projection th The audit chain remains the source of truth in both cases. +## Integrity verification (checker scope) + +The usagestore is **deliberately excluded from `internal/application/check/checker.go`**. Invariant #8 ("every persisted projection must be verified by the checker") is scoped to projections that live in the **primary** Pebble store — the store that participates in Pebble snapshots, backups and cold-storage, and that an operator cannot rebuild without stopping the cluster. The usagestore, like the read store (`readstore`), is a physically separate secondary Pebble instance at `/usage/`: it is never snapshotted, never backed up, and is trivially rebuildable offline via `ledgerctl store rebuild-usage` (drop the directory + replay from audit sequence 0). A tampered or corrupted usagestore is therefore not a durable integrity vector — the next rebuild reconstructs it from the hash-chained audit, which the checker *does* verify. Extending the checker to walk a peer store would couple it to a subsystem it has no authority over and duplicate the rebuild logic that already re-derives every counter from the same source of truth. + ## Summary | Concern | Mechanism | File | diff --git a/internal/adapter/http/handlers_get_numscript_usage.go b/internal/adapter/http/handlers_get_numscript_usage.go index 8543d052ce..9df08da6f8 100644 --- a/internal/adapter/http/handlers_get_numscript_usage.go +++ b/internal/adapter/http/handlers_get_numscript_usage.go @@ -27,7 +27,9 @@ func toTemplateUsageJSON(usage *commonpb.TemplateUsage) *templateUsageJSON { out := &templateUsageJSON{Count: usage.GetCount()} if ts := usage.GetLastUsed(); ts != nil && ts.GetData() != 0 { - formatted := time.Unix(0, int64(ts.GetData())).UTC().Format(time.RFC3339Nano) + // Timestamp.Data is Unix microseconds — use the canonical AsTime() + // converter (time.UnixMicro) rather than treating Data as nanoseconds. + formatted := ts.AsTime().UTC().Format(time.RFC3339Nano) out.LastUsed = &formatted } diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index eecf9ca613..5b8e12f975 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -100,7 +100,7 @@ func (s *batchState) addTemplateUsage(ledger, template string, ts *commonpb.Time } // timestampGreater reports whether a > b in wall-clock ordering. Both -// operands are non-nil. commonpb.Timestamp encodes nanoseconds-since-epoch +// operands are non-nil. commonpb.Timestamp encodes microseconds-since-epoch // as a single uint64 field (data), so ordering is direct integer compare. func timestampGreater(a, b *commonpb.Timestamp) bool { return a.GetData() > b.GetData() @@ -619,12 +619,25 @@ func (b *Builder) commitBatch(state *batchState, cursor uint64) error { // Counter deltas: read-modify-write against the usagestore. Not the // FSM's Pebble — invariant #3 does not apply here. for ledger, counters := range state.counters { - for counterID, delta := range counters { - current, err := b.usageStore.GetCounter(ledger, counterID) - if err != nil { - _ = batch.Cancel() + // If this batch also deleted the ledger, the DeleteRange enqueued + // above logically zeroes every counter for the recycled name. But + // GetCounter reads the committed DB, not the pending batch, so it + // would return the OLD incarnation's value and we'd write + // old+delta on top of the DeleteRange — resurrecting stale counts + // for a same-batch delete+recreate. Treat the baseline as 0 for a + // deleted ledger so only the post-recreate deltas survive. + _, deleted := state.deletedLedgers[ledger] - return fmt.Errorf("reading counter %#x for ledger %q: %w", counterID, ledger, err) + for counterID, delta := range counters { + var current uint64 + if !deleted { + var err error + current, err = b.usageStore.GetCounter(ledger, counterID) + if err != nil { + _ = batch.Cancel() + + return fmt.Errorf("reading counter %#x for ledger %q: %w", counterID, ledger, err) + } } next := applyDelta(current, delta) @@ -640,11 +653,19 @@ func (b *Builder) commitBatch(state *batchState, cursor uint64) error { // Template deltas: same read-modify-write pattern on the TemplateUsage // proto. count is additive; last_used is max(previous, batch max). for k, delta := range state.templates { - current, err := b.usageStore.GetTemplateUsage(k.ledger, k.template) - if err != nil { - _ = batch.Cancel() + // Same same-batch delete+recreate hazard as counters above: for a + // ledger this batch deleted, the DeleteRange zeroes the recycled + // name, so ignore the persisted (old incarnation) value and start + // from a nil baseline. + var current *commonpb.TemplateUsage + if _, deleted := state.deletedLedgers[k.ledger]; !deleted { + var err error + current, err = b.usageStore.GetTemplateUsage(k.ledger, k.template) + if err != nil { + _ = batch.Cancel() - return fmt.Errorf("reading template usage %q/%q: %w", k.ledger, k.template, err) + return fmt.Errorf("reading template usage %q/%q: %w", k.ledger, k.template, err) + } } next := mergeTemplateUsage(current, delta) diff --git a/internal/application/usagebuilder/process_audit_test.go b/internal/application/usagebuilder/process_audit_test.go index a749464b8b..7f467a0862 100644 --- a/internal/application/usagebuilder/process_audit_test.go +++ b/internal/application/usagebuilder/process_audit_test.go @@ -4,6 +4,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" "github.com/formancehq/ledger/v3/internal/proto/commonpb" "github.com/formancehq/ledger/v3/internal/storage/usagestore" @@ -118,3 +121,52 @@ func TestMergeTemplateUsage_WithCurrent(t *testing.T) { assert.Equal(t, uint64(11), got.GetCount()) assert.Equal(t, uint64(500), got.GetLastUsed().GetData()) } + +// TestCommitBatch_SameBatchDeleteRecreateDoesNotResurrect guards the +// same-batch delete+recreate hazard: GetCounter / GetTemplateUsage read the +// committed DB, not the pending batch's DeleteRange, so without a zero +// baseline for deleted ledgers commitBatch would write old+delta on top of +// the DeleteRange and resurrect the old incarnation's counts. +func TestCommitBatch_SameBatchDeleteRecreateDoesNotResurrect(t *testing.T) { + t.Parallel() + + us, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) + require.NoError(t, err) + t.Cleanup(func() { _ = us.Close() }) + + b := &Builder{usageStore: us} + + // Seed the OLD incarnation: 100 postings and a template with count 7. + seed := newBatchState() + seed.addCounter("foo", usagestore.CounterPosting, 100) + seed.addTemplateUsage("foo", "tpl", &commonpb.Timestamp{Data: 111}) + for range 6 { + seed.addTemplateUsage("foo", "tpl", &commonpb.Timestamp{Data: 111}) + } + require.NoError(t, b.commitBatch(seed, 1)) + + // Sanity: the old incarnation is persisted. + c, err := us.GetCounter("foo", usagestore.CounterPosting) + require.NoError(t, err) + require.Equal(t, uint64(100), c) + + // A single batch deletes "foo" and then recreates it with 5 new + // postings + a single template invocation. + batch := newBatchState() + batch.markLedgerDeleted("foo") + batch.addCounter("foo", usagestore.CounterPosting, 5) + batch.addTemplateUsage("foo", "tpl", &commonpb.Timestamp{Data: 999}) + require.NoError(t, b.commitBatch(batch, 2)) + + // The recycled ledger must reflect ONLY the post-recreate deltas, not + // old+delta. + c, err = us.GetCounter("foo", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(5), c, "counter must not resurrect the deleted incarnation's value") + + tpl, err := us.GetTemplateUsage("foo", "tpl") + require.NoError(t, err) + require.NotNil(t, tpl) + assert.Equal(t, uint64(1), tpl.GetCount(), "template count must not resurrect the deleted incarnation") + assert.Equal(t, uint64(999), tpl.GetLastUsed().GetData()) +} From 92fe77ab10c2b6a7171801d6ff9cd55e2ff0424f Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 17:01:09 +0200 Subject: [PATCH 15/35] fix(EN-1334): rewind usage cursor on primary-store rollback (blocker) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boot guard failed loud when the persisted usage cursor was ahead of the audit head (primary store rolled back while the usage dir was retained), but that guard is only transient: once new activity grows the audit head back past the stale cursor, `cursor <= pebbleLast` holds again, the guard stops firing, and every audit entry in the rolled-back gap is silently skipped forever — counters stay permanently ahead. Rewind in place instead: add usagestore.Store.Reset() (wipe every counter + template row and clear the progress cursor) and have Builder.boot() call it when it detects cursor > audit head, resetting the in-memory cursor to 0 so catch-up replays the current audit chain from the start. Self-healing, same net effect as `ledgerctl store rebuild-usage` without operator intervention. Also drops the hand-rolled discardLogger fake in store_test.go (repo forbids interface fakes) in favour of logging.NopZap(), matching readstore's audit_index_test.go, and adds a TestStore_Reset regression. --- internal/application/usagebuilder/builder.go | 30 ++++++--- internal/storage/usagestore/store.go | 48 ++++++++++++-- internal/storage/usagestore/store_test.go | 68 +++++++++++++------- 3 files changed, 108 insertions(+), 38 deletions(-) diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 2bfb99a32b..15c28c91d9 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -165,16 +165,28 @@ func (b *Builder) boot(ctx context.Context) error { if sampleErr == nil { // Cursor ahead of the audit head means the primary Pebble store // was restored to an earlier checkpoint (or wiped) while the - // usagestore was kept. Silently resuming would leave stale - // counter / template rows for the rolled-back entries visible - // forever. Fail loud instead — the recovery path is - // `ledgerctl store rebuild-usage`, which drops the usagestore - // and replays from audit sequence 0. + // usagestore was kept. The retained counter / template rows now + // reflect audit entries that no longer exist, and — critically — + // merely failing the boot is not enough: once new activity grows + // the audit head back past the stale cursor, `cursor <= pebbleLast` + // holds again, the guard stops firing, and every entry in the + // rolled-back gap is silently skipped forever (the counters stay + // permanently ahead). Rewind in place instead: wipe the projection + // and reset the cursor to 0 so catch-up replays the current audit + // chain from the start. Same net effect as `ledgerctl store + // rebuild-usage`, but self-healing without operator intervention. if cursor > pebbleLast { - return fmt.Errorf( - "usage cursor (%d) is ahead of the audit head (%d): the primary store appears to have been restored to an earlier checkpoint — run `ledgerctl store rebuild-usage` to rebuild the usage projections from the current audit chain", - cursor, pebbleLast, - ) + b.logger.WithFields(map[string]any{ + "cursor": cursor, + "pebbleLast": pebbleLast, + }).Infof("usage cursor is ahead of the audit head — primary store was rolled back; resetting usage projection and rebuilding from audit sequence 0") + + if err := b.usageStore.Reset(); err != nil { + return fmt.Errorf("resetting usage store after primary-store rollback: %w", err) + } + + cursor = 0 + b.lastProcessedAuditSeq.Store(0) } b.pebbleLastAuditSeq.Store(pebbleLast) diff --git a/internal/storage/usagestore/store.go b/internal/storage/usagestore/store.go index cb57827ac3..b5b0f6f3ce 100644 --- a/internal/storage/usagestore/store.go +++ b/internal/storage/usagestore/store.go @@ -17,15 +17,12 @@ import ( "github.com/formancehq/ledger/v3/internal/storage/pebblecfg" ) -// Config is the Pebble configuration for the usage store. -// Reuses the same tunables as the primary store (pebblecfg.Config). -type Config = pebblecfg.Config - // DefaultConfig returns the default Pebble configuration for the usage store. +// Reuses the same tunables type as the primary store (pebblecfg.Config). // Sized smaller than the read index: the usage store holds O(ledgers × templates) // entries plus a handful of per-ledger counters, so it never grows large. -func DefaultConfig() Config { - return Config{ +func DefaultConfig() pebblecfg.Config { + return pebblecfg.Config{ MemTableSize: 16 << 20, // 16MB MemTableStopWritesThreshold: 4, L0CompactionThreshold: 4, @@ -50,7 +47,7 @@ type Store struct { } // New opens or creates a Pebble database at the given directory. -func New(dir string, logger logging.Logger, cfg Config) (*Store, error) { +func New(dir string, logger logging.Logger, cfg pebblecfg.Config) (*Store, error) { if err := os.MkdirAll(dir, 0o750); err != nil { return nil, fmt.Errorf("creating usage store directory: %w", err) } @@ -191,6 +188,43 @@ func (s *Store) WriteProgress(batch *dal.WriteSession, sequence uint64) error { return batch.SetBytes(ProgressKey(), buf[:]) } +// Reset wipes every projection row (all per-template usage records and all +// per-ledger counters) and clears the persisted progress cursor, so the next +// boot replays from audit sequence 0. Used when the builder detects that the +// primary store was rolled back beneath the persisted cursor: the retained +// rows reflect audit entries that no longer exist, so a clean in-place rebuild +// is the only way to reconverge without an operator running rebuild-usage. +// +// The two ledger-scoped prefixes (PrefixTemplate 0x01, PrefixCounter 0x02) are +// contiguous, so one DeleteRange over [0x01, 0x03) covers both; the internal +// progress key ([0xFE][0x01]) is deleted point-wise. A crash mid-reset is +// safe: the cursor is either still ahead (rollback re-detected next boot) or +// already gone (replay from 0), so the rows can never survive with a stale +// non-zero cursor. +func (s *Store) Reset() error { + batch := s.NewBatch() + + if err := batch.DeleteRangeNoSync([]byte{PrefixTemplate}, []byte{PrefixCounter + 1}); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("deleting projection rows during reset: %w", err) + } + + if err := batch.DeleteKey(ProgressKey()); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("deleting progress cursor during reset: %w", err) + } + + if err := batch.Commit(); err != nil { + _ = batch.Cancel() + + return fmt.Errorf("committing usage store reset: %w", err) + } + + return nil +} + // GetTemplateUsage reads the current usage record for (ledger, template). // Returns (nil, nil) if no entry exists. func (s *Store) GetTemplateUsage(ledgerName, templateName string) (*commonpb.TemplateUsage, error) { diff --git a/internal/storage/usagestore/store_test.go b/internal/storage/usagestore/store_test.go index b53d5a98ad..18d611b359 100644 --- a/internal/storage/usagestore/store_test.go +++ b/internal/storage/usagestore/store_test.go @@ -1,8 +1,6 @@ package usagestore_test import ( - "context" - "io" "testing" "github.com/stretchr/testify/assert" @@ -17,7 +15,7 @@ import ( func newTestStore(t *testing.T) *usagestore.Store { t.Helper() - s, err := usagestore.New(t.TempDir(), discardLogger{}, usagestore.DefaultConfig()) + s, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) require.NoError(t, err) t.Cleanup(func() { _ = s.Close() }) @@ -134,22 +132,48 @@ func TestStore_DeleteLedgerCascade(t *testing.T) { assert.Equal(t, uint64(5), tu.GetCount()) } -// discardLogger mirrors readstore's test helper (see index_version_test.go). -// Not exported so each secondary store test package owns its own. -type discardLogger struct{} - -var _ logging.Logger = discardLogger{} - -func (discardLogger) Tracef(string, ...any) {} -func (discardLogger) Debugf(string, ...any) {} -func (discardLogger) Infof(string, ...any) {} -func (discardLogger) Errorf(string, ...any) {} -func (discardLogger) Trace(...any) {} -func (discardLogger) Debug(...any) {} -func (discardLogger) Info(...any) {} -func (discardLogger) Error(...any) {} -func (l discardLogger) WithFields(map[string]any) logging.Logger { return l } -func (l discardLogger) WithField(string, any) logging.Logger { return l } -func (l discardLogger) WithContext(context.Context) logging.Logger { return l } -func (discardLogger) Writer() io.Writer { return io.Discard } -func (discardLogger) Enabled(logging.Level) bool { return false } +// TestStore_Reset guards the primary-store-rollback recovery path: Reset must +// wipe every counter + template row across all ledgers AND clear the progress +// cursor so the builder replays from audit sequence 0. +func TestStore_Reset(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + // Seed counters + templates for two ledgers, plus a progress cursor + // simulating a projection that had consumed 500 audit entries. + batch := s.NewBatch() + require.NoError(t, s.PutCounter(batch, "l1", usagestore.CounterPosting, 10)) + require.NoError(t, s.PutCounter(batch, "l1", usagestore.CounterVolume, 3)) + require.NoError(t, s.PutTemplateUsage(batch, "l1", "t1", &commonpb.TemplateUsage{Count: 3})) + require.NoError(t, s.PutCounter(batch, "l2", usagestore.CounterPosting, 20)) + require.NoError(t, s.PutTemplateUsage(batch, "l2", "t2", &commonpb.TemplateUsage{Count: 5})) + require.NoError(t, s.WriteProgress(batch, 500)) + require.NoError(t, batch.Commit()) + + require.NoError(t, s.Reset()) + + // Every counter across both ledgers reads 0. + for _, ledger := range []string{"l1", "l2"} { + for _, counter := range []byte{usagestore.CounterPosting, usagestore.CounterVolume} { + v, err := s.GetCounter(ledger, counter) + require.NoError(t, err) + assert.Equal(t, uint64(0), v, "counter %#x for %q must be wiped by Reset", counter, ledger) + } + } + + // Every template row is gone. + for _, tk := range []struct{ ledger, tpl string }{{"l1", "t1"}, {"l2", "t2"}} { + tu, err := s.GetTemplateUsage(tk.ledger, tk.tpl) + require.NoError(t, err) + assert.Nil(t, tu, "template %q/%q must be wiped by Reset", tk.ledger, tk.tpl) + } + + // The cursor is back to 0 so the next boot replays from the start. + seq, err := s.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(0), seq, "Reset must clear the progress cursor") + + // Reset on an already-empty store is a no-op, not an error. + require.NoError(t, s.Reset()) +} From bb7ece0678914974aa8c4fc322e4f21875a4cc2c Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 17:01:36 +0200 Subject: [PATCH 16/35] fix(EN-1334): reserve deleted LedgerBoundaries field numbers 3-10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fields 3-10 (the per-ledger counters migrated to the usagestore) were removed without reservation. The vtprotobuf unmarshaller retains unknown-field bytes and re-emits them on MarshalVT, so an old persisted Pebble record carrying e.g. volume_count at tag 3 would be mis-decoded if a new field later reused tag 3. Reserve the 8 tags and their former names. Regenerated raft_cmd.pb.go carries only the descriptor-level reservation — no Go API change. --- internal/proto/raftcmdpb/raft_cmd.pb.go | 6 ++++-- misc/proto/raft_cmd.proto | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/proto/raftcmdpb/raft_cmd.pb.go b/internal/proto/raftcmdpb/raft_cmd.pb.go index d3d0dccb7c..63481c2667 100644 --- a/internal/proto/raftcmdpb/raft_cmd.pb.go +++ b/internal/proto/raftcmdpb/raft_cmd.pb.go @@ -6358,10 +6358,12 @@ const file_raft_cmd_proto_rawDesc = "" + "\vcreated_log\x18\x01 \x01(\v2\v.common.LogH\x00R\n" + "createdLog\x12/\n" + "\x12reference_sequence\x18\x02 \x01(\x06H\x00R\x11referenceSequenceB\x06\n" + - "\x04type\"b\n" + + "\x04type\"\xa8\x02\n" + "\x10LedgerBoundaries\x12.\n" + "\x13next_transaction_id\x18\x01 \x01(\x06R\x11nextTransactionId\x12\x1e\n" + - "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogId\"\\\n" + + "\vnext_log_id\x18\x02 \x01(\x06R\tnextLogIdJ\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\t\x10\n" + + "J\x04\b\n" + + "\x10\vR\fvolume_countR\x0emetadata_countR\x0freference_countR\rposting_countR\x17ephemeral_evicted_countR\x14transient_used_countR\frevert_countR\x19numscript_execution_count\"\\\n" + "\n" + "VolumePair\x12%\n" + "\x05input\x18\x01 \x01(\v2\x0f.common.Uint256R\x05input\x12'\n" + diff --git a/misc/proto/raft_cmd.proto b/misc/proto/raft_cmd.proto index 26b9bdf2c8..5ec73673ec 100644 --- a/misc/proto/raft_cmd.proto +++ b/misc/proto/raft_cmd.proto @@ -666,6 +666,19 @@ message CreatedLogOrReference { message LedgerBoundaries { fixed64 next_transaction_id = 1; fixed64 next_log_id = 2; + + // Fields 3-10 held per-ledger counters (volume_count, metadata_count, + // reference_count, posting_count, ephemeral_evicted_count, + // transient_used_count, revert_count, numscript_execution_count) that + // migrated to the usagestore projection (EN-1420 / EN-1422). They are + // reserved — never reuse these tags: the vtprotobuf unmarshaller retains + // unknown-field bytes and re-emits them on MarshalVT, so an old persisted + // Pebble record carrying e.g. volume_count at tag 3 would be mis-decoded + // as whatever new field claimed tag 3. + reserved 3, 4, 5, 6, 7, 8, 9, 10; + reserved "volume_count", "metadata_count", "reference_count", + "posting_count", "ephemeral_evicted_count", "transient_used_count", + "revert_count", "numscript_execution_count"; } message VolumePair { From 8c6f1a2116a61c3210a53e01da3a84f893c97c84 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 17:01:36 +0200 Subject: [PATCH 17/35] refactor(EN-1334): drop prohibited type aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo conventions forbid type aliases. Remove `Config = pebblecfg.Config` in usagestore (use pebblecfg.Config directly) and convert `counterDelta = int64` in the usagebuilder into a real defined type — the signed-delta semantics (volume evictions decrement, applyDelta's underflow clamp) are load-bearing, so a distinct named type is the right fit rather than plain int64. --- internal/application/usagebuilder/process_audit.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 5b8e12f975..68c70085ce 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -29,9 +29,12 @@ type templateDelta struct { } // counterDelta is a signed delta for a per-ledger event counter. Deltas are -// always non-negative today (all counters are monotonically increasing) but -// int64 leaves room for future decrement paths (e.g. a rollback log type). -type counterDelta = int64 +// almost always non-negative (event counters are monotonically increasing) but +// the volume counter can decrement (a draining eviction subtracts 1), so the +// underlying type is signed. Defined as a distinct type — not a type alias, +// which the repository conventions forbid — because the signed-delta semantics +// are load-bearing (see applyDelta's underflow clamp). +type counterDelta int64 // batchState holds the in-flight aggregation for one batch: per-ledger // counter deltas, per-template usage deltas, and the set of ledger names From f63b3aa52a9c8015c90968df2b50ed1c35a8f4cb Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 17:01:36 +0200 Subject: [PATCH 18/35] fix(EN-1334): propagate spinner Start errors in rebuild-usage pterm's Spinner.Start returns (nil, err) on a live-renderer init failure; the four `spinner, _ := ... .Start()` sites discarded the error then dereferenced the nil spinner (Fail/Success would panic). Route every start through a startSpinner helper that wraps and returns the error. --- cmd/ledgerctl/store/rebuild_usage.go | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index 9c44e8e52e..e8ce65f1b1 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -160,7 +160,10 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { // Drop the existing usage store so the builder starts at cursor=0 and // no stale counter survives the rebuild. - spinner, _ := pterm.DefaultSpinner.Start("Dropping existing usage store...") + spinner, err := startSpinner("Dropping existing usage store...") + if err != nil { + return cmdutil.Displayed(err) + } if err := os.RemoveAll(usageDir); err != nil { spinner.Fail("Failed to drop usage store directory") @@ -173,7 +176,10 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { // Open primary Pebble read-only — same as rebuild-indexes / // rebuild-audit-index (the live SSTs are at /live, not // dataDir itself). - spinner, _ = pterm.DefaultSpinner.Start("Opening Pebble store (read-only)...") + spinner, err = startSpinner("Opening Pebble store (read-only)...") + if err != nil { + return cmdutil.Displayed(err) + } pebbleStore, err := dal.OpenReadOnly(filepath.Join(dataDir, "live"), logger) if err != nil { @@ -187,7 +193,10 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { spinner.Success("Pebble store opened") // Create the fresh usage store. - spinner, _ = pterm.DefaultSpinner.Start("Creating usage store...") + spinner, err = startSpinner("Creating usage store...") + if err != nil { + return cmdutil.Displayed(err) + } us, err := usagestore.New(usageDir, logger, usagestore.DefaultConfig()) if err != nil { @@ -201,7 +210,10 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { spinner.Success("Usage store created at " + us.Path()) // Rebuild — notifications is nil in offline mode (no FSM running). - spinner, _ = pterm.DefaultSpinner.Start("Rebuilding usage projections from the audit chain...") + spinner, err = startSpinner("Rebuilding usage projections from the audit chain...") + if err != nil { + return cmdutil.Displayed(err) + } builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize) @@ -216,3 +228,15 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { return nil } + +// startSpinner starts a live-renderer spinner and propagates any +// initialization error instead of discarding it — a failed Start returns a nil +// *SpinnerPrinter, so dereferencing it (Fail/Success) would panic. +func startSpinner(text string) (*pterm.SpinnerPrinter, error) { + spinner, err := pterm.DefaultSpinner.Start(text) + if err != nil { + return nil, fmt.Errorf("starting spinner: %w", err) + } + + return spinner, nil +} From 1ebccf4f58783f83f437646ca1a38216f01b362f Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 18:42:28 +0200 Subject: [PATCH 19/35] fix(EN-1334): emit epoch lastUsed in numscript-usage response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit toTemplateUsageJSON gated on `ts != nil && ts.GetData() != 0`, so a valid lastUsed at the Unix epoch (Data==0) was silently dropped from the HTTP response — even though nil already encodes "never used" via the omitempty pointer. Gate on `ts != nil` only. Adds regression coverage: epoch lastUsed present + formatted, nil omitted, and locks the two sibling contract points (camelCase DTO with no raw protobuf tags; Data read as microseconds not nanoseconds). --- .../http/handlers_get_numscript_usage.go | 6 +- .../http/handlers_get_numscript_usage_test.go | 64 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 internal/adapter/http/handlers_get_numscript_usage_test.go diff --git a/internal/adapter/http/handlers_get_numscript_usage.go b/internal/adapter/http/handlers_get_numscript_usage.go index 9df08da6f8..78d62484c2 100644 --- a/internal/adapter/http/handlers_get_numscript_usage.go +++ b/internal/adapter/http/handlers_get_numscript_usage.go @@ -26,7 +26,11 @@ type templateUsageJSON struct { func toTemplateUsageJSON(usage *commonpb.TemplateUsage) *templateUsageJSON { out := &templateUsageJSON{Count: usage.GetCount()} - if ts := usage.GetLastUsed(); ts != nil && ts.GetData() != 0 { + // Gate on nil only: a non-nil Timestamp is a real value even when Data==0 + // (the Unix epoch, 1970-01-01T00:00:00Z). nil already encodes "never + // used" via the omitempty pointer — folding Data==0 into that branch + // would silently drop a legitimate epoch lastUsed from the response. + if ts := usage.GetLastUsed(); ts != nil { // Timestamp.Data is Unix microseconds — use the canonical AsTime() // converter (time.UnixMicro) rather than treating Data as nanoseconds. formatted := ts.AsTime().UTC().Format(time.RFC3339Nano) diff --git a/internal/adapter/http/handlers_get_numscript_usage_test.go b/internal/adapter/http/handlers_get_numscript_usage_test.go new file mode 100644 index 0000000000..2e6f9b65c6 --- /dev/null +++ b/internal/adapter/http/handlers_get_numscript_usage_test.go @@ -0,0 +1,64 @@ +package http + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" +) + +// TestToTemplateUsageJSON_EpochLastUsed is the regression guard for the +// dropped-epoch bug: a non-nil lastUsed at Data==0 (the Unix epoch) must be +// emitted, not silently omitted. nil is the only "never used" sentinel. +func TestToTemplateUsageJSON_EpochLastUsed(t *testing.T) { + t.Parallel() + + out := toTemplateUsageJSON(&commonpb.TemplateUsage{ + Count: 3, + LastUsed: &commonpb.Timestamp{Data: 0}, + }) + + require.NotNil(t, out.LastUsed, "a non-nil epoch timestamp must be present, not omitted") + assert.Equal(t, "1970-01-01T00:00:00Z", *out.LastUsed, "Data==0 is the Unix epoch, formatted from microseconds") + assert.Equal(t, uint64(3), out.Count) +} + +// TestToTemplateUsageJSON_NilLastUsedOmitted confirms nil (never invoked) +// still drops lastUsed from the JSON (omitempty on the pointer). +func TestToTemplateUsageJSON_NilLastUsedOmitted(t *testing.T) { + t.Parallel() + + out := toTemplateUsageJSON(&commonpb.TemplateUsage{Count: 0}) + require.Nil(t, out.LastUsed) + + raw, err := json.Marshal(out) + require.NoError(t, err) + assert.JSONEq(t, `{"count":0}`, string(raw), "nil lastUsed must be omitted, not serialized as null") +} + +// TestToTemplateUsageJSON_MicrosecondUnitAndCamelCase locks the two sibling +// contract points flemzord flagged alongside the epoch bug: +// - the DTO serializes camelCase (`lastUsed`, `count`) with no raw protobuf +// tags (`last_used`) or wire encoding (`{data: }`); +// - Timestamp.Data is interpreted as microseconds, not nanoseconds. +func TestToTemplateUsageJSON_MicrosecondUnitAndCamelCase(t *testing.T) { + t.Parallel() + + // 1_700_000_000_000_000 µs = 2023-11-14T22:13:20Z. If Data were treated + // as nanoseconds the year would collapse to 1970. + out := toTemplateUsageJSON(&commonpb.TemplateUsage{ + Count: 42, + LastUsed: &commonpb.Timestamp{Data: 1_700_000_000_000_000}, + }) + + require.NotNil(t, out.LastUsed) + assert.Equal(t, "2023-11-14T22:13:20Z", *out.LastUsed, "Data must be read as microseconds") + + raw, err := json.Marshal(out) + require.NoError(t, err) + assert.JSONEq(t, `{"count":42,"lastUsed":"2023-11-14T22:13:20Z"}`, string(raw), + "DTO must be camelCase with no raw protobuf field names or wire encoding") +} From 2a190667c157ac50a491f459fdbfbff89f0528a2 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 18:47:30 +0200 Subject: [PATCH 20/35] fix(EN-1334): rewind usage cursor on runtime follower-sync restore The boot() rollback guard runs only once, before the ticker. The steady-state tick() re-sampled the audit head but never compared it to the cursor, so an in-place primary-store restore (follower sync via SynchronizeWithLeader / RestoreCheckpoint) that drops the audit head below the persisted cursor WHILE the builder is running left the same corruption window open: once the restored head grew back past the cursor, the rolled-back gap was skipped forever. Extract the boot rollback logic into rewindIfCursorAhead (+ a cursorAheadOfHead predicate) and call it from both boot() and tick(), mirroring auditindexer.processTick's per-tick re-check. Reuses the Store.Reset() rewind already added for boot (DRY). +TestRewindIfCursorAhead_RuntimeRestore / _SteadyStateNoOp. --- internal/application/usagebuilder/builder.go | 81 ++++++++++---- .../application/usagebuilder/builder_test.go | 103 ++++++++++++++++++ 2 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 internal/application/usagebuilder/builder_test.go diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 15c28c91d9..cc8c2b26d4 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -163,30 +163,12 @@ func (b *Builder) boot(ctx context.Context) error { pebbleLast, sampleErr := b.sampleAuditHead() if sampleErr == nil { - // Cursor ahead of the audit head means the primary Pebble store - // was restored to an earlier checkpoint (or wiped) while the - // usagestore was kept. The retained counter / template rows now - // reflect audit entries that no longer exist, and — critically — - // merely failing the boot is not enough: once new activity grows - // the audit head back past the stale cursor, `cursor <= pebbleLast` - // holds again, the guard stops firing, and every entry in the - // rolled-back gap is silently skipped forever (the counters stay - // permanently ahead). Rewind in place instead: wipe the projection - // and reset the cursor to 0 so catch-up replays the current audit - // chain from the start. Same net effect as `ledgerctl store - // rebuild-usage`, but self-healing without operator intervention. - if cursor > pebbleLast { - b.logger.WithFields(map[string]any{ - "cursor": cursor, - "pebbleLast": pebbleLast, - }).Infof("usage cursor is ahead of the audit head — primary store was rolled back; resetting usage projection and rebuilding from audit sequence 0") - - if err := b.usageStore.Reset(); err != nil { - return fmt.Errorf("resetting usage store after primary-store rollback: %w", err) - } - + rewound, err := b.rewindIfCursorAhead(cursor, pebbleLast) + if err != nil { + return err + } + if rewound { cursor = 0 - b.lastProcessedAuditSeq.Store(0) } b.pebbleLastAuditSeq.Store(pebbleLast) @@ -239,17 +221,68 @@ func (b *Builder) boot(ctx context.Context) error { // tick runs one steady-state iteration: refresh the audit-head gauge, then // drain any pending audit entries from the persisted cursor forward. +// +// The cursor-ahead re-check is what catches a runtime primary-store restore +// (follower sync via SynchronizeWithLeader/RestoreCheckpoint) that drops the +// audit head below the persisted cursor WHILE this loop is already running. +// The boot() guard runs only once before the ticker, so without re-evaluating +// here processAuditEntries would resume from the stale in-memory cursor and +// silently skip every entry in the rolled-back gap forever. Same self-healing +// rewind as boot(). Mirrors auditindexer.processTick. func (b *Builder) tick(ctx context.Context) error { + cursor := b.lastProcessedAuditSeq.Load() + if last, err := b.sampleAuditHead(); err == nil { + rewound, rewindErr := b.rewindIfCursorAhead(cursor, last) + if rewindErr != nil { + return rewindErr + } + if rewound { + cursor = 0 + } + b.pebbleLastAuditSeq.Store(last) } - cursor := b.lastProcessedAuditSeq.Load() _, err := b.processAuditEntries(ctx, cursor, time.Time{}) return err } +// cursorAheadOfHead reports the post-restore rollback signature: the persisted +// usage cursor sits beyond the current audit head. Only possible after the +// primary Pebble store was restored/truncated to an earlier head while the +// usagestore directory was retained (boot after a backup restore, or a runtime +// follower sync via SynchronizeWithLeader/RestoreCheckpoint). +func cursorAheadOfHead(cursor, head uint64) bool { return cursor > head } + +// rewindIfCursorAhead resets the usage projection and rewinds the cursor to 0 +// when the persisted cursor has overtaken the audit head. The retained counter +// / template rows reflect audit entries that no longer exist, so a clean +// in-place rebuild is the only way to reconverge. Merely failing loud is not +// enough: once new activity grows the head back past the stale cursor the +// signature disappears and the rolled-back gap would be skipped forever. +// Returns true when a rewind was performed (caller must treat the cursor as 0). +// Shared by boot() and tick() so both entry points heal identically (DRY). +func (b *Builder) rewindIfCursorAhead(cursor, head uint64) (bool, error) { + if !cursorAheadOfHead(cursor, head) { + return false, nil + } + + b.logger.WithFields(map[string]any{ + "cursor": cursor, + "head": head, + }).Infof("usage cursor is ahead of the audit head — primary store was rolled back; resetting usage projection and rebuilding from audit sequence 0") + + if err := b.usageStore.Reset(); err != nil { + return false, fmt.Errorf("resetting usage store after primary-store rollback: %w", err) + } + + b.lastProcessedAuditSeq.Store(0) + + return true, nil +} + // sampleAuditHead opens a short-lived read handle to read the current audit // head. The handle is closed immediately so RestoreCheckpoint's write lock // is not blocked during idle ticks. diff --git a/internal/application/usagebuilder/builder_test.go b/internal/application/usagebuilder/builder_test.go new file mode 100644 index 0000000000..440312576c --- /dev/null +++ b/internal/application/usagebuilder/builder_test.go @@ -0,0 +1,103 @@ +package usagebuilder + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +func newBuilderWithUsageStore(t *testing.T) (*Builder, *usagestore.Store) { + t.Helper() + + us, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) + require.NoError(t, err) + t.Cleanup(func() { _ = us.Close() }) + + return &Builder{usageStore: us, logger: logging.NopZap()}, us +} + +// TestRewindIfCursorAhead_RuntimeRestore is the regression guard for the +// follower-sync corruption window: a primary-store restore (RestoreCheckpoint) +// drops the audit head below the persisted cursor WHILE the builder is running. +// tick() re-evaluates the cursor-ahead signature each pass and must rewind — +// wiping the stale rows and resetting the cursor to 0 — so the rolled-back gap +// is re-processed rather than skipped forever. +func TestRewindIfCursorAhead_RuntimeRestore(t *testing.T) { + t.Parallel() + + b, us := newBuilderWithUsageStore(t) + + // Simulate a projection that had consumed up to audit seq 500: stale + // counter + template rows plus a cursor at 500. + batch := us.NewBatch() + require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) + require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterVolume, 7)) + require.NoError(t, us.PutTemplateUsage(batch, "l1", "t1", &commonpb.TemplateUsage{Count: 9})) + require.NoError(t, us.WriteProgress(batch, 500)) + require.NoError(t, batch.Commit()) + + b.lastProcessedAuditSeq.Store(500) + + // Primary store was restored to head 120 (< cursor 500). + rewound, err := b.rewindIfCursorAhead(500, 120) + require.NoError(t, err) + require.True(t, rewound, "cursor 500 ahead of head 120 must trigger a rewind") + + // Projection wiped. + c, err := us.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(0), c, "stale posting counter must be wiped") + + v, err := us.GetCounter("l1", usagestore.CounterVolume) + require.NoError(t, err) + assert.Equal(t, uint64(0), v, "stale volume counter must be wiped") + + tu, err := us.GetTemplateUsage("l1", "t1") + require.NoError(t, err) + assert.Nil(t, tu, "stale template row must be wiped") + + // Persisted cursor and the in-memory hint both reset to 0 so catch-up + // re-processes the surviving audit chain from the start. + seq, err := us.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(0), seq, "persisted cursor must rewind to 0") + assert.Equal(t, uint64(0), b.lastProcessedAuditSeq.Load(), "in-memory cursor hint must rewind to 0") +} + +// TestRewindIfCursorAhead_SteadyStateNoOp confirms the common case — cursor at +// or behind the audit head — leaves the projection and cursor untouched. +func TestRewindIfCursorAhead_SteadyStateNoOp(t *testing.T) { + t.Parallel() + + b, us := newBuilderWithUsageStore(t) + + batch := us.NewBatch() + require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) + require.NoError(t, us.WriteProgress(batch, 100)) + require.NoError(t, batch.Commit()) + b.lastProcessedAuditSeq.Store(100) + + // Head 300 is ahead of cursor 100 — normal steady state, no rewind. + rewound, err := b.rewindIfCursorAhead(100, 300) + require.NoError(t, err) + require.False(t, rewound) + + c, err := us.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(42), c, "counter must survive when cursor is not ahead") + + seq, err := us.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(100), seq, "cursor must be untouched when not ahead") + + // Cursor exactly at head is also steady state (not ahead). + rewound, err = b.rewindIfCursorAhead(300, 300) + require.NoError(t, err) + assert.False(t, rewound, "cursor == head is not ahead") +} From b18fb63b902ed5917f1688dccb424acd236df11a Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 18:50:18 +0200 Subject: [PATCH 21/35] docs(EN-1334): make usagestore counter checker-scope an explicit design choice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit counters.md previously (a) wrongly claimed the audit hash chain covers the whole LedgerLog, and (b) called a usagestore CounterVolume checker pass a vague "follow-up". Reframe both: the hash chain binds only the audit header + per-item order index / log sequence / serialized order (not LedgerLog content), and the absence of a usagestore checker pass is a deliberate scope decision — the checker verifies primary-store projections; the usagestore is a rebuildable peer side-store whose recovery contract is a rebuild from the (cryptographically verified) audit chain. The primary-store-relevant exclusion set (Purged ∪ Ephemeral) is already verified by compareExclusionProjections; NewKeptVolumes has no primary-store consumer. Audit-derived usagestore coverage remains separately scoped under EN-1422. --- .../architecture/subsystems/usage/counters.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md index 0a1bd4c54b..fde7bb06e2 100644 --- a/docs/technical/architecture/subsystems/usage/counters.md +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -81,7 +81,7 @@ The disjoint encoding pays exactly `len(ephemeral)` per log instead of `2 × len 3. `makeNewKeptKeySet(partResult.kept)` (new) yields the subset of `kept` where `Old` was undefined / zero-placeholder — i.e. new persistent volumes. 4. `buildTouchedByLog(volumes.Slots(), setX)` (new, generalised from the previous `buildPurgedByLog`) intersects the per-order touched-volume tracking with each of the three sets to produce the per-log annotation lists. Deduplication + deterministic sort by (account, asset) keeps the log payload byte-identical across nodes. -The three lists are injected into each `LedgerLog` inside the same `createdLogs` build loop that also injects `purged_volumes`. The audit hash chain covers the whole `LedgerLog` so any drift is detected by the checker. +The three lists are injected into each `LedgerLog` inside the same `createdLogs` build loop that also injects `purged_volumes`. Note the audit hash chain does **not** cover `LedgerLog` content — it binds the audit header plus each item's order index, log sequence, and serialized order (see [Checker consumer](#checker-consumer) for what this means for tamper detection of the derived counters). ### The preload contract this depends on @@ -115,9 +115,18 @@ Full keyspace conventions in `internal/storage/usagestore/keys.go`. The `[ledger ### Checker consumer -`compareExclusionProjections` (`internal/application/check/checker.go`) accumulates `PurgedVolumes` and `EphemeralVolumes` from every log into the stored projection set, then compares against the exclusion set derived by replaying the audit chain (`AppliedProposal.TransientVolumes` union). Both eviction lists have identical semantics for the checker — the split is a pure log-payload compaction, not an invariant change. +`compareExclusionProjections` (`internal/application/check/checker.go`) accumulates `PurgedVolumes` and `EphemeralVolumes` from every log into the stored projection set, then compares against the exclusion set derived by replaying the audit chain (`AppliedProposal.TransientVolumes` union). Both eviction lists have identical semantics for the checker — the split is a pure log-payload compaction, not an invariant change. This pass verifies the exclusion projection *in the primary store* — the set the indexbuilder consumes. -A dedicated checker pass for `CounterVolume` (replay `sum(new_kept − purged)` and compare against the usagestore value) is a follow-up — the template exists in `compareExclusionProjections`. +### Why the usagestore counters are not a checker target (design decision, not a gap) + +The checker (invariant #8) verifies projections persisted **in the primary Pebble store** — the store it operates on: `Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, idempotency outcomes, the index registry, and the exclusion projection above. The usagestore is a **distinct, peer secondary Pebble instance** (`/usage/`) holding a *derived cache* of counters, fully rebuildable from the audit chain on demand (`ledgerctl store rebuild-usage`, or the automatic boot/tick rewind). Its authoritativeness is explicitly bounded by "eventually consistent with the FSM". + +Consequently, the three volume-annotation categories are deliberately **not** given a dedicated usagestore checker pass: + +- `NewKeptVolumes` has **no** primary-store consumer — it feeds only `CounterVolume` in the usagestore. +- The primary-store-relevant signal, the exclusion set `PurgedVolumes ∪ EphemeralVolumes` consumed by the indexbuilder, **is** already verified by `compareExclusionProjections` above. The indexbuilder consumes the union and is indifferent to the Purged-vs-Ephemeral split, so the union is the correct verification granularity for the primary store. + +Corruption of a usagestore counter is therefore a *rebuild*, not an integrity incident: the recovery contract is "drop and replay from the audit chain", and the audit chain itself **is** cryptographically verified (`verifyAuditHashChain`). Serving a derived, rebuildable value through `GetLedgerStats` / `GetTemplateUsage` does not make it authoritative primary-store state. Extending audit-derived tamper coverage to the usagestore counters (which would require threading a new-volume collector through the shared `internal/domain/replay` package, also used by backup restore) is a separately-scoped effort tracked under EN-1422 — not a prerequisite for this subsystem. ## Metrics From d611d0b19da8c6d843a2283970eaf3e2c1f6ca36 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 20:22:04 +0200 Subject: [PATCH 22/35] fix(EN-1334): add auditindexer-parity gap heuristic to usage cursor guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cursor>head guard (boot + tick) misses a subtle race: if a primary-store restore lowers the audit head, then Raft/follower-sync re-appends past the old cursor BEFORE the builder samples the head, cursorAheadOfHead never fires and the rolled-back gap is skipped forever. Add the same secondary net the auditindexer already has: a boot-time gapExceedsThreshold check (head − cursor > rebuildThreshold) that resets + replays when the anomalous gap is observed even though cursor is behind head. Reuses the auditindexer semantics/constant — NewBuilder takes a rebuildThreshold wired from the shared cfg.AuditIndexConfig.RebuildThreshold (both subsystems tail the same audit chain). Gap check is boot-only, exactly like auditindexer.shouldRebuildOnBoot vs processTick, so a normal ingest burst between ticks does not spuriously rebuild. Full audit-chain-fingerprint hardening (cursor bound to Raft term+index / checkpoint hash) remains a separate cross-cutting follow-up for BOTH the usagebuilder and auditindexer — not a #1464 blocker. +TestRewindOnRollback_BootGapThreshold. --- cmd/ledgerctl/store/rebuild_usage.go | 4 +- internal/application/usagebuilder/builder.go | 79 ++++++++++++++----- .../application/usagebuilder/builder_test.go | 58 ++++++++++++-- internal/bootstrap/module.go | 9 ++- 4 files changed, 122 insertions(+), 28 deletions(-) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index e8ce65f1b1..6d29dbb6f4 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -215,7 +215,9 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { return cmdutil.Displayed(err) } - builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize) + // rebuildThreshold is irrelevant offline: RebuildAll replays from cursor 0 + // on a freshly-dropped store, so the boot gap heuristic never applies. + builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize, 0) lastSeq, err := builder.RebuildAll() if err != nil { diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index cc8c2b26d4..c1c451ec82 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -61,6 +61,14 @@ type Builder struct { batchSize int + // rebuildThreshold triggers a boot-time reset+replay when the audit head + // leads the persisted cursor by more than this many entries (0 disables + // the gap heuristic). Mirrors auditindexer.Config.RebuildThreshold — it is + // the secondary net that catches a primary-store rollback whose restored + // gap re-grew past the cursor before boot sampled the head, so the momentary + // cursor>head signature was never observable. See rewindIfCursorAhead. + rebuildThreshold uint64 + // lastProcessedAuditSeq mirrors usagestore.ReadProgress() and is // updated on every successful commit — the atomic hint lets external // readers (metrics, tests) avoid a Pebble Get. @@ -83,18 +91,20 @@ func NewBuilder( logger logging.Logger, meter metric.Meter, batchSize int, + rebuildThreshold uint64, ) *Builder { if batchSize <= 0 { batchSize = DefaultBatchSize } return &Builder{ - pebbleStore: pebbleStore, - usageStore: usageStore, - notifications: notifications, - logger: logger.WithFields(map[string]any{"cmp": "usage-builder"}), - meter: meter, - batchSize: batchSize, + pebbleStore: pebbleStore, + usageStore: usageStore, + notifications: notifications, + logger: logger.WithFields(map[string]any{"cmp": "usage-builder"}), + meter: meter, + batchSize: batchSize, + rebuildThreshold: rebuildThreshold, } } @@ -163,7 +173,11 @@ func (b *Builder) boot(ctx context.Context) error { pebbleLast, sampleErr := b.sampleAuditHead() if sampleErr == nil { - rewound, err := b.rewindIfCursorAhead(cursor, pebbleLast) + // Boot uses the full rewind test: cursor-ahead OR an over-threshold + // gap. The gap branch is the auditindexer-parity net for a rollback + // whose restored gap re-grew past the cursor before we sampled the + // head, so cursor>head was never observable. + rewound, err := b.rewindOnRollback(cursor, pebbleLast, true) if err != nil { return err } @@ -233,7 +247,11 @@ func (b *Builder) tick(ctx context.Context) error { cursor := b.lastProcessedAuditSeq.Load() if last, err := b.sampleAuditHead(); err == nil { - rewound, rewindErr := b.rewindIfCursorAhead(cursor, last) + // Steady-state uses the cursor-ahead test ONLY (boot=false): the + // over-threshold gap branch is a boot-only heuristic — applying it + // per tick would spuriously rebuild whenever a normal burst outpaces + // the drain between ticks. Same split as auditindexer.processTick. + rewound, rewindErr := b.rewindOnRollback(cursor, last, false) if rewindErr != nil { return rewindErr } @@ -256,23 +274,46 @@ func (b *Builder) tick(ctx context.Context) error { // follower sync via SynchronizeWithLeader/RestoreCheckpoint). func cursorAheadOfHead(cursor, head uint64) bool { return cursor > head } -// rewindIfCursorAhead resets the usage projection and rewinds the cursor to 0 -// when the persisted cursor has overtaken the audit head. The retained counter -// / template rows reflect audit entries that no longer exist, so a clean -// in-place rebuild is the only way to reconverge. Merely failing loud is not -// enough: once new activity grows the head back past the stale cursor the -// signature disappears and the rolled-back gap would be skipped forever. +// gapExceedsThreshold reports the auditindexer-parity secondary net: the audit +// head leads the cursor by more than rebuildThreshold entries. This catches a +// primary-store rollback whose restored gap re-grew past the cursor before the +// head was sampled — so cursorAheadOfHead never fired — by observing the +// resulting anomalous head−cursor gap instead. Mirrors the RebuildThreshold +// branch of auditindexer.shouldRebuildOnBoot. Boot-only (see rewindOnRollback). +func (b *Builder) gapExceedsThreshold(cursor, head uint64) bool { + return b.rebuildThreshold > 0 && head > cursor && head-cursor > b.rebuildThreshold +} + +// rewindOnRollback resets the usage projection and rewinds the cursor to 0 when +// a primary-store rollback is detected. The retained counter / template rows +// reflect audit entries that no longer exist, so a clean in-place rebuild is the +// only way to reconverge. Merely failing loud is not enough: once new activity +// grows the head back past the stale cursor the cursor-ahead signature +// disappears and the rolled-back gap would be skipped forever. +// +// Two signatures, mirroring auditindexer: +// - cursorAheadOfHead (both boot and tick): the direct rollback signature. +// - gapExceedsThreshold (boot only): the secondary net for a rollback whose +// restored gap re-grew past the cursor before the head was sampled. It is +// boot-only because per-tick it would spuriously rebuild whenever a normal +// ingest burst outpaces the drain between ticks (see auditindexer.processTick). +// // Returns true when a rewind was performed (caller must treat the cursor as 0). // Shared by boot() and tick() so both entry points heal identically (DRY). -func (b *Builder) rewindIfCursorAhead(cursor, head uint64) (bool, error) { - if !cursorAheadOfHead(cursor, head) { +func (b *Builder) rewindOnRollback(cursor, head uint64, boot bool) (bool, error) { + ahead := cursorAheadOfHead(cursor, head) + gap := boot && b.gapExceedsThreshold(cursor, head) + + if !ahead && !gap { return false, nil } b.logger.WithFields(map[string]any{ - "cursor": cursor, - "head": head, - }).Infof("usage cursor is ahead of the audit head — primary store was rolled back; resetting usage projection and rebuilding from audit sequence 0") + "cursor": cursor, + "head": head, + "cursorAhead": ahead, + "gapExceeded": gap, + }).Infof("primary store appears rolled back — resetting usage projection and rebuilding from audit sequence 0") if err := b.usageStore.Reset(); err != nil { return false, fmt.Errorf("resetting usage store after primary-store rollback: %w", err) diff --git a/internal/application/usagebuilder/builder_test.go b/internal/application/usagebuilder/builder_test.go index 440312576c..7724809f56 100644 --- a/internal/application/usagebuilder/builder_test.go +++ b/internal/application/usagebuilder/builder_test.go @@ -44,8 +44,9 @@ func TestRewindIfCursorAhead_RuntimeRestore(t *testing.T) { b.lastProcessedAuditSeq.Store(500) - // Primary store was restored to head 120 (< cursor 500). - rewound, err := b.rewindIfCursorAhead(500, 120) + // Primary store was restored to head 120 (< cursor 500). The steady-state + // (boot=false) path detects the direct cursor-ahead signature. + rewound, err := b.rewindOnRollback(500, 120, false) require.NoError(t, err) require.True(t, rewound, "cursor 500 ahead of head 120 must trigger a rewind") @@ -83,8 +84,9 @@ func TestRewindIfCursorAhead_SteadyStateNoOp(t *testing.T) { require.NoError(t, batch.Commit()) b.lastProcessedAuditSeq.Store(100) - // Head 300 is ahead of cursor 100 — normal steady state, no rewind. - rewound, err := b.rewindIfCursorAhead(100, 300) + // Head 300 is ahead of cursor 100 — normal steady state, no rewind. No + // rebuildThreshold configured, so the gap heuristic is inert too. + rewound, err := b.rewindOnRollback(100, 300, false) require.NoError(t, err) require.False(t, rewound) @@ -97,7 +99,53 @@ func TestRewindIfCursorAhead_SteadyStateNoOp(t *testing.T) { assert.Equal(t, uint64(100), seq, "cursor must be untouched when not ahead") // Cursor exactly at head is also steady state (not ahead). - rewound, err = b.rewindIfCursorAhead(300, 300) + rewound, err = b.rewindOnRollback(300, 300, false) require.NoError(t, err) assert.False(t, rewound, "cursor == head is not ahead") } + +// TestRewindOnRollback_BootGapThreshold is the auditindexer-parity regression: +// after an in-place primary-store restore, the restored gap can re-grow past +// the stale cursor before boot samples the head, so cursorAheadOfHead never +// fires. The boot-only gap heuristic (head−cursor > rebuildThreshold) is the +// secondary net that still triggers the reset/replay. +func TestRewindOnRollback_BootGapThreshold(t *testing.T) { + t.Parallel() + + b, us := newBuilderWithUsageStore(t) + b.rebuildThreshold = 1000 + + // Stale cursor at 500 with a leftover row; the restored-then-caught-up + // head is 5000 — head > cursor (so cursorAheadOfHead is FALSE), but the + // 4500-entry gap exceeds the threshold. + batch := us.NewBatch() + require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterVolume, 3)) + require.NoError(t, us.WriteProgress(batch, 500)) + require.NoError(t, batch.Commit()) + b.lastProcessedAuditSeq.Store(500) + + // Boot path (boot=true): the gap heuristic fires even though cursor Date: Sun, 12 Jul 2026 21:58:59 +0200 Subject: [PATCH 23/35] fix(EN-1334): detect primary-store rollback via restore generation (blocker) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cursor>head position signal (cursorAheadOfHead + gapExceedsThreshold / shouldRebuildOnBoot) can be erased by a catch-up race: the primary store is restored beneath the poller cursor, then the audit head re-grows past the old cursor before the poller re-samples, so cursorhead position signal + // (restore below cursor, head re-grows past the old cursor before this tick + // re-samples). Rebuild re-syncs restoreGen, so this fires once per restore. + if gen := i.store.RestoreGeneration(); gen != i.restoreGen.Load() { + i.logger.WithFields(map[string]any{"from": i.restoreGen.Load(), "to": gen}). + Infof("Audit index rebuild: primary store restore detected (generation changed)") + + return i.Rebuild(ctx) + } + if last, err := i.lastAuditSequence(); err == nil { i.auditLast.Store(last) diff --git a/internal/application/auditindexer/indexer_test.go b/internal/application/auditindexer/indexer_test.go index 03adba5016..7dc7a5eed2 100644 --- a/internal/application/auditindexer/indexer_test.go +++ b/internal/application/auditindexer/indexer_test.go @@ -383,3 +383,65 @@ func TestProcessTickRebuildsWhenCursorAheadOfHead(t *testing.T) { require.NoError(t, err) require.Equal(t, []uint64{1, 2, 3}, seqs, "surviving entries must be re-indexed after rollback") } + +// TestProcessTickRebuildsOnRestoreGenerationChange is the regression for the +// catch-up race the cursor>head signal cannot catch: the primary store is +// restored beneath the cursor, then the audit head re-grows PAST the old cursor +// before the tick re-samples — so cursorAheadOfHead is false and the gap is +// under the (default-disabled) threshold. The restore-generation change is the +// only remaining rollback signal, and processTick must rebuild on it. +func TestProcessTickRebuildsOnRestoreGenerationChange(t *testing.T) { + t.Parallel() + ctx := context.Background() + idx, mainStore, rs := newIndexerForTest(t) + + // Seed and index entries 1..5, leaving cursor at 5. + for s := uint64(1); s <= 5; s++ { + writeAuditEntry(t, mainStore, &auditpb.AuditEntry{ + Sequence: s, ProposalId: s, Timestamp: &commonpb.Timestamp{Data: s * 1_000_000}, + Outcome: &auditpb.AuditEntry_Success{Success: &auditpb.AuditSuccess{}}, + Ledgers: []string{"main"}, + }) + } + + // boot seeds the restore generation; ProcessOnce indexes 1..5. + require.NoError(t, idx.boot(ctx)) + cursor, err := rs.ReadAuditProgress() + require.NoError(t, err) + require.Equal(t, uint64(5), cursor) + + // Simulate the rollback+catch-up: a real RestoreCheckpoint bumps the store + // generation (the runtime follower-sync signature), and the post-restore + // head re-grows to 10 — strictly ABOVE the cursor (5), so the position + // signal cannot fire. + checkpointID, err := mainStore.CreateSnapshot() + require.NoError(t, err) + require.NoError(t, mainStore.RestoreCheckpoint(checkpointID)) + for s := uint64(6); s <= 10; s++ { + writeAuditEntry(t, mainStore, &auditpb.AuditEntry{ + Sequence: s, ProposalId: s, Timestamp: &commonpb.Timestamp{Data: s * 1_000_000}, + Outcome: &auditpb.AuditEntry_Success{Success: &auditpb.AuditSuccess{}}, + Ledgers: []string{"post"}, + }) + } + + last, err := idx.lastAuditSequence() + require.NoError(t, err) + require.Equal(t, uint64(10), last) + require.False(t, cursorAheadOfHead(5, last), "head re-grew past cursor: position signal is inert") + require.False(t, idx.shouldRebuildOnBoot(5, last), "gap under default-disabled threshold: no position signal") + + // processTick must still rebuild — driven purely by the generation change — + // and re-index the full surviving chain from 0. + require.NoError(t, idx.processTick(ctx)) + + cursor, err = rs.ReadAuditProgress() + require.NoError(t, err) + require.Equal(t, uint64(10), cursor, "rebuild re-indexes the full surviving chain") + + // A second tick with no further restore must NOT rebuild (generation resync). + require.NoError(t, idx.processTick(ctx)) + cursor, err = rs.ReadAuditProgress() + require.NoError(t, err) + require.Equal(t, uint64(10), cursor, "no rebuild without a new restore") +} diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index c1c451ec82..a3d90b8261 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -77,6 +77,15 @@ type Builder struct { // resampled on each tick for the lag gauge. pebbleLastAuditSeq atomic.Uint64 + // restoreGen is the store restore generation this builder last processed + // under (see dal.Store.RestoreGeneration). Seeded on boot, re-read every + // tick: a change means the primary store was rolled back beneath the cursor + // at runtime, so a reset+rebuild is forced regardless of where the audit + // head landed relative to the cursor — the position-based cursorAheadOfHead + // / gapExceedsThreshold signals can be erased by a catch-up race. Mirrors + // auditindexer.Indexer.restoreGen. + restoreGen atomic.Uint64 + tw *tailworker.TailWorker reg metric.Registration } @@ -164,6 +173,10 @@ func (b *Builder) PebbleLastAuditSequence() uint64 { // loop (returned to tailworker, which logs and stops); a catch-up error is // logged and swallowed so steady-state indexing still starts. func (b *Builder) boot(ctx context.Context) error { + // Snapshot the restore generation before any processing so tick() can detect + // a runtime rollback that lands after boot. + b.restoreGen.Store(b.pebbleStore.RestoreGeneration()) + cursor, err := b.usageStore.ReadProgress() if err != nil { return fmt.Errorf("reading usage progress: %w", err) @@ -246,6 +259,23 @@ func (b *Builder) boot(ctx context.Context) error { func (b *Builder) tick(ctx context.Context) error { cursor := b.lastProcessedAuditSeq.Load() + // Restore-generation change is the authoritative rollback signal: it fires + // on any RestoreCheckpoint regardless of where the audit head landed, so it + // catches the catch-up race that erases the cursor>head position signal + // (restore below cursor, head re-grows past the old cursor before this tick + // re-samples). Checked before the position heuristic and independent of the + // head sample. Mirrors auditindexer.processTick. + if gen := b.pebbleStore.RestoreGeneration(); gen != b.restoreGen.Load() { + b.logger.WithFields(map[string]any{"from": b.restoreGen.Load(), "to": gen}). + Infof("primary store restore detected (generation changed) — resetting usage projection and rebuilding from audit sequence 0") + + if err := b.resetProjection(); err != nil { + return err + } + + cursor = 0 + } + if last, err := b.sampleAuditHead(); err == nil { // Steady-state uses the cursor-ahead test ONLY (boot=false): the // over-threshold gap branch is a boot-only heuristic — applying it @@ -315,13 +345,29 @@ func (b *Builder) rewindOnRollback(cursor, head uint64, boot bool) (bool, error) "gapExceeded": gap, }).Infof("primary store appears rolled back — resetting usage projection and rebuilding from audit sequence 0") + if err := b.resetProjection(); err != nil { + return false, err + } + + return true, nil +} + +// resetProjection wipes the usagestore and rewinds the cursor to 0, re-syncing +// the restore generation up-front so a RestoreCheckpoint that lands during the +// subsequent replay bumps past this value and is re-detected on the next tick +// (rather than being silently swallowed). Shared by the generation-change and +// position-based rewind paths (DRY). Mirrors auditindexer.Rebuild's generation +// re-sync. +func (b *Builder) resetProjection() error { + b.restoreGen.Store(b.pebbleStore.RestoreGeneration()) + if err := b.usageStore.Reset(); err != nil { - return false, fmt.Errorf("resetting usage store after primary-store rollback: %w", err) + return fmt.Errorf("resetting usage store after primary-store rollback: %w", err) } b.lastProcessedAuditSeq.Store(0) - return true, nil + return nil } // sampleAuditHead opens a short-lived read handle to read the current audit diff --git a/internal/application/usagebuilder/builder_test.go b/internal/application/usagebuilder/builder_test.go index 7724809f56..4ebf2e0052 100644 --- a/internal/application/usagebuilder/builder_test.go +++ b/internal/application/usagebuilder/builder_test.go @@ -1,25 +1,84 @@ package usagebuilder import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/metric/noop" + "google.golang.org/protobuf/proto" logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + "github.com/formancehq/ledger/v3/internal/proto/auditpb" "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/storage/dal" "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) func newBuilderWithUsageStore(t *testing.T) (*Builder, *usagestore.Store) { t.Helper() - us, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) + // Back the builder with a real (empty) primary store so the shared reset + // path (resetProjection → RestoreGeneration) is exercised without a nil + // dereference. The position-based rewind tests pass (cursor, head) directly, + // so the empty store's audit head is never sampled. + b, _, us := newBuilderWithStores(t) + + return b, us +} + +// newBuilderWithStores wires a Builder over a real primary dal.Store and a real +// usagestore, so tests can exercise the restore-generation path (which reads +// pebbleStore.RestoreGeneration and samples the audit head). +func newBuilderWithStores(t *testing.T) (*Builder, *dal.Store, *usagestore.Store) { + t.Helper() + + logger := logging.NopZap() + meter := noop.NewMeterProvider().Meter("test") + + main, err := dal.NewStore(t.TempDir(), logger, meter, dal.DefaultConfig()) + require.NoError(t, err) + t.Cleanup(func() { _ = main.Close() }) + + us, err := usagestore.New(t.TempDir(), logger, usagestore.DefaultConfig()) require.NoError(t, err) t.Cleanup(func() { _ = us.Close() }) - return &Builder{usageStore: us, logger: logging.NopZap()}, us + return &Builder{ + pebbleStore: main, + usageStore: us, + logger: logger, + batchSize: DefaultBatchSize, + }, main, us +} + +// writeAuditEntry appends a minimal failed audit entry so the audit head +// advances without an AppliedProposal / items projection: the usagebuilder +// skips failed proposals (no state delta), which is all this test needs to +// drive the cursor forward past the generation-triggered reset. +func writeAuditEntry(t *testing.T, store *dal.Store, seq uint64) { + t.Helper() + + entry := &auditpb.AuditEntry{ + Sequence: seq, + ProposalId: seq, + Timestamp: &commonpb.Timestamp{Data: seq * 1_000_000}, + Outcome: &auditpb.AuditEntry_Failure{Failure: &auditpb.AuditFailure{}}, + Ledgers: []string{"l1"}, + } + + val, err := proto.Marshal(entry) + require.NoError(t, err) + + batch := store.OpenWriteSession() + kb := dal.NewKeyBuilder() + require.NoError(t, batch.SetBytes( + kb.PutZonePrefix(dal.ZoneCold, dal.SubColdAudit).PutUint64(seq).Build(), + val, + )) + require.NoError(t, batch.Commit()) } // TestRewindIfCursorAhead_RuntimeRestore is the regression guard for the @@ -149,3 +208,65 @@ func TestRewindOnRollback_BootGapThreshold(t *testing.T) { require.NoError(t, err) assert.False(t, rewound, "gap within threshold must not trigger a rewind") } + +// TestTickResetsOnRestoreGenerationChange is the regression for the catch-up +// race the cursor>head signal cannot catch: the primary store is restored +// beneath the usage cursor, then the audit head re-grows PAST the old cursor +// before tick() re-samples — so cursorAheadOfHead is false. The restore- +// generation change is the only remaining rollback signal, and tick() must +// reset the projection and rebuild from 0. +func TestTickResetsOnRestoreGenerationChange(t *testing.T) { + t.Parallel() + + b, main, us := newBuilderWithStores(t) + ctx := context.Background() + + // Seed the surviving audit chain (1..3) into the primary store and take a + // checkpoint — this is the state a runtime restore will roll back to. + for s := uint64(1); s <= 3; s++ { + writeAuditEntry(t, main, s) + } + checkpointID, err := main.CreateSnapshot() + require.NoError(t, err) + + // A projection that had already consumed up to audit seq 3, with stale rows. + batch := us.NewBatch() + require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) + require.NoError(t, us.WriteProgress(batch, 3)) + require.NoError(t, batch.Commit()) + + // boot seeds the generation baseline; head (3) == cursor (3), so no rewind. + require.NoError(t, b.boot(ctx)) + require.Equal(t, uint64(3), b.lastProcessedAuditSeq.Load()) + + // Runtime rollback: a real RestoreCheckpoint bumps the store generation, and + // the post-restore head re-grows to 5 — strictly ABOVE the stale cursor (3), + // so cursorAheadOfHead is inert and only the generation change can fire. + require.NoError(t, main.RestoreCheckpoint(checkpointID)) + for s := uint64(4); s <= 5; s++ { + writeAuditEntry(t, main, s) + } + + head, err := b.sampleAuditHead() + require.NoError(t, err) + require.Equal(t, uint64(5), head) + require.False(t, cursorAheadOfHead(3, head), "head re-grew past cursor: position signal is inert") + + // tick() must reset on the generation change and re-drain the surviving + // chain from 0, landing the cursor back at the head. + require.NoError(t, b.tick(ctx)) + + seq, err := us.ReadProgress() + require.NoError(t, err) + require.Equal(t, uint64(5), seq, "reset+rebuild must re-consume the full surviving chain") + + c, err := us.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(0), c, "stale counter must be wiped by the reset") + + // A second tick with no further restore must NOT reset (generation resync). + require.NoError(t, b.tick(ctx)) + seq, err = us.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(5), seq, "no reset without a new restore") +} diff --git a/internal/storage/dal/store.go b/internal/storage/dal/store.go index 99cb5f1b8b..f21f41fdda 100644 --- a/internal/storage/dal/store.go +++ b/internal/storage/dal/store.go @@ -11,6 +11,7 @@ import ( "runtime" "strconv" "sync" + "sync/atomic" "time" "github.com/cockroachdb/pebble/v2" @@ -108,6 +109,20 @@ type Store struct { maxCheckpoints int stallState *WriteStallState iopsCounters *IOPSCounters + + // restoreGeneration is a process-local monotonic counter bumped on every + // successful RestoreCheckpoint. It lets audit-chain pollers (auditindexer, + // usagebuilder) detect that the primary store was rolled back beneath their + // cursor WITHOUT relying on the cursor>head position signal, which a + // catch-up race can erase (restore below cursor, then the head re-grows past + // the old cursor before the poller re-samples). A poller records the value + // it observed and forces a reset+rebuild the moment it changes. It is + // in-memory by design: RestoreCheckpoint only runs at runtime (follower sync + // via SynchronizeWithLeader), where the pollers keep running across the + // restore (StopBackgroundTasks does not stop them); a process restart resets + // both the store and the poller to 0, falling back to the boot-time + // cursor-ahead / gap heuristic that covers offline backup restores. + restoreGeneration atomic.Uint64 } // getDB returns the current pebble.DB. @@ -1399,6 +1414,12 @@ func (s *Store) RestoreCheckpoint(checkpointID uint64) error { s.currentCheckPoint = checkpointID s.oldestCheckpoint = checkpointID + // Bump the restore generation so audit-chain pollers detect this rollback + // regardless of where the audit head lands relative to their cursor. Done + // under dbMu.Lock (still held via the deferred unlock) so it is ordered + // after the publish; the atomic lets lock-free pollers read it. + s.restoreGeneration.Add(1) + s.logger.WithFields(map[string]any{ "checkpointId": checkpointID, }).Infof("Database restored from checkpoint") @@ -1406,6 +1427,16 @@ func (s *Store) RestoreCheckpoint(checkpointID uint64) error { return nil } +// RestoreGeneration returns the process-local count of successful +// RestoreCheckpoint calls. Audit-chain pollers (auditindexer, usagebuilder) +// snapshot this at boot and compare on every tick: a change means the primary +// store was rolled back beneath their cursor, so they must reset+rebuild even +// when the cursor>head position signal was erased by a catch-up race. See the +// restoreGeneration field comment. +func (s *Store) RestoreGeneration() uint64 { + return s.restoreGeneration.Load() +} + // IterateColdKVPairs iterates every cold-storable KV pair belonging to a // chapter via efficient prefixed range scans. // diff --git a/internal/storage/dal/store_restore_rollback_test.go b/internal/storage/dal/store_restore_rollback_test.go index 82401c23a9..c684718ee0 100644 --- a/internal/storage/dal/store_restore_rollback_test.go +++ b/internal/storage/dal/store_restore_rollback_test.go @@ -245,3 +245,36 @@ func TestReconcileLiveAfterRestore_CleansUpWhenBothExist(t *testing.T) { _, err = os.Stat(discardDirectory) require.True(t, os.IsNotExist(err), "reconciliation must drop the stale live.discard") } + +// TestRestoreGenerationIncrementsOnSuccessfulRestore is the store-side contract +// the audit-chain pollers (auditindexer, usagebuilder) depend on to detect a +// runtime primary-store rollback that the cursor>head position signal can miss: +// a successful RestoreCheckpoint must bump RestoreGeneration, and a failed one +// must not. +func TestRestoreGenerationIncrementsOnSuccessfulRestore(t *testing.T) { + t.Parallel() + + s := newTestStore(t) + + require.Equal(t, uint64(0), s.RestoreGeneration(), "generation starts at 0") + + // Seed data and take a checkpoint. + batch := s.OpenWriteSession() + require.NoError(t, batch.SetBytes([]byte("k"), []byte("v"))) + require.NoError(t, batch.Commit()) + + checkpointID, err := s.CreateSnapshot() + require.NoError(t, err) + + // A successful restore bumps the generation. + require.NoError(t, s.RestoreCheckpoint(checkpointID)) + require.Equal(t, uint64(1), s.RestoreGeneration(), "successful restore must bump generation") + + // A failed restore (unknown checkpoint) must NOT bump the generation. + require.Error(t, s.RestoreCheckpoint(999_999)) + require.Equal(t, uint64(1), s.RestoreGeneration(), "failed restore must not bump generation") + + // A second successful restore bumps again (monotonic). + require.NoError(t, s.RestoreCheckpoint(checkpointID)) + require.Equal(t, uint64(2), s.RestoreGeneration(), "each successful restore bumps monotonically") +} From fa5fe6ea277c475f8a28b23f587aa8c00cf13d90 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 22:48:17 +0200 Subject: [PATCH 24/35] Revert "fix(EN-1334): detect primary-store rollback via restore generation (blocker)" This reverts commit 9923f9693ccb289bb84b9e7aa779442ebed2a362. --- internal/application/auditindexer/indexer.go | 29 ---- .../application/auditindexer/indexer_test.go | 62 --------- internal/application/usagebuilder/builder.go | 50 +------ .../application/usagebuilder/builder_test.go | 125 +----------------- internal/storage/dal/store.go | 31 ----- .../dal/store_restore_rollback_test.go | 33 ----- 6 files changed, 4 insertions(+), 326 deletions(-) diff --git a/internal/application/auditindexer/indexer.go b/internal/application/auditindexer/indexer.go index 779294b26b..e7bb67b4aa 100644 --- a/internal/application/auditindexer/indexer.go +++ b/internal/application/auditindexer/indexer.go @@ -62,14 +62,6 @@ type Indexer struct { // Updated on each tick by lastAuditSequence(); used only for metric gauges. auditLast atomic.Uint64 - // restoreGen is the store restore generation this indexer last processed - // under (see dal.Store.RestoreGeneration). Seeded on boot, re-read every - // tick: a change means the primary store was rolled back beneath the cursor - // at runtime, so a full rebuild is forced regardless of where the audit head - // landed relative to the cursor — the position-based cursorAheadOfHead / - // RebuildThreshold signals can be erased by a catch-up race. - restoreGen atomic.Uint64 - // tw drives the steady-state tail loop (boot + ticker). tw *tailworker.TailWorker @@ -141,11 +133,6 @@ func (i *Indexer) ProcessOnce(ctx context.Context) (uint64, error) { // Rebuild drops the audit index and the cursor, then replays from the earliest // surviving audit entry. Used by ledgerctl and by boot auto-rebuild. func (i *Indexer) Rebuild(ctx context.Context) error { - // Snapshot the restore generation up-front: a RestoreCheckpoint that lands - // while this rebuild is replaying bumps past this value, so the next - // processTick re-detects it instead of the rebuild silently swallowing it. - i.restoreGen.Store(i.store.RestoreGeneration()) - // Drop the index and reset the cursor in a single batch so the operation is // crash-atomic: a torn write leaves either (old index, old cursor) or (empty // index, cursor 0). The latter deterministically re-triggers boot rebuild @@ -317,10 +304,6 @@ func (i *Indexer) Stop() { // (returned to tailworker, which logs and stops); a rebuild error is logged // and swallowed so steady-state indexing still starts. func (i *Indexer) boot(ctx context.Context) error { - // Snapshot the restore generation before any processing so processTick can - // detect a runtime rollback that lands after boot. - i.restoreGen.Store(i.store.RestoreGeneration()) - cursor, err := i.readStore.ReadAuditProgress() if err != nil { return fmt.Errorf("read audit cursor: %w", err) @@ -350,18 +333,6 @@ func (i *Indexer) boot(ctx context.Context) error { // would spuriously trigger a full rebuild whenever a normal burst exceeds the // threshold between ticks. func (i *Indexer) processTick(ctx context.Context) error { - // Restore-generation change is the authoritative rollback signal: it fires - // on any RestoreCheckpoint regardless of where the audit head landed, so it - // catches the catch-up race that erases the cursor>head position signal - // (restore below cursor, head re-grows past the old cursor before this tick - // re-samples). Rebuild re-syncs restoreGen, so this fires once per restore. - if gen := i.store.RestoreGeneration(); gen != i.restoreGen.Load() { - i.logger.WithFields(map[string]any{"from": i.restoreGen.Load(), "to": gen}). - Infof("Audit index rebuild: primary store restore detected (generation changed)") - - return i.Rebuild(ctx) - } - if last, err := i.lastAuditSequence(); err == nil { i.auditLast.Store(last) diff --git a/internal/application/auditindexer/indexer_test.go b/internal/application/auditindexer/indexer_test.go index 7dc7a5eed2..03adba5016 100644 --- a/internal/application/auditindexer/indexer_test.go +++ b/internal/application/auditindexer/indexer_test.go @@ -383,65 +383,3 @@ func TestProcessTickRebuildsWhenCursorAheadOfHead(t *testing.T) { require.NoError(t, err) require.Equal(t, []uint64{1, 2, 3}, seqs, "surviving entries must be re-indexed after rollback") } - -// TestProcessTickRebuildsOnRestoreGenerationChange is the regression for the -// catch-up race the cursor>head signal cannot catch: the primary store is -// restored beneath the cursor, then the audit head re-grows PAST the old cursor -// before the tick re-samples — so cursorAheadOfHead is false and the gap is -// under the (default-disabled) threshold. The restore-generation change is the -// only remaining rollback signal, and processTick must rebuild on it. -func TestProcessTickRebuildsOnRestoreGenerationChange(t *testing.T) { - t.Parallel() - ctx := context.Background() - idx, mainStore, rs := newIndexerForTest(t) - - // Seed and index entries 1..5, leaving cursor at 5. - for s := uint64(1); s <= 5; s++ { - writeAuditEntry(t, mainStore, &auditpb.AuditEntry{ - Sequence: s, ProposalId: s, Timestamp: &commonpb.Timestamp{Data: s * 1_000_000}, - Outcome: &auditpb.AuditEntry_Success{Success: &auditpb.AuditSuccess{}}, - Ledgers: []string{"main"}, - }) - } - - // boot seeds the restore generation; ProcessOnce indexes 1..5. - require.NoError(t, idx.boot(ctx)) - cursor, err := rs.ReadAuditProgress() - require.NoError(t, err) - require.Equal(t, uint64(5), cursor) - - // Simulate the rollback+catch-up: a real RestoreCheckpoint bumps the store - // generation (the runtime follower-sync signature), and the post-restore - // head re-grows to 10 — strictly ABOVE the cursor (5), so the position - // signal cannot fire. - checkpointID, err := mainStore.CreateSnapshot() - require.NoError(t, err) - require.NoError(t, mainStore.RestoreCheckpoint(checkpointID)) - for s := uint64(6); s <= 10; s++ { - writeAuditEntry(t, mainStore, &auditpb.AuditEntry{ - Sequence: s, ProposalId: s, Timestamp: &commonpb.Timestamp{Data: s * 1_000_000}, - Outcome: &auditpb.AuditEntry_Success{Success: &auditpb.AuditSuccess{}}, - Ledgers: []string{"post"}, - }) - } - - last, err := idx.lastAuditSequence() - require.NoError(t, err) - require.Equal(t, uint64(10), last) - require.False(t, cursorAheadOfHead(5, last), "head re-grew past cursor: position signal is inert") - require.False(t, idx.shouldRebuildOnBoot(5, last), "gap under default-disabled threshold: no position signal") - - // processTick must still rebuild — driven purely by the generation change — - // and re-index the full surviving chain from 0. - require.NoError(t, idx.processTick(ctx)) - - cursor, err = rs.ReadAuditProgress() - require.NoError(t, err) - require.Equal(t, uint64(10), cursor, "rebuild re-indexes the full surviving chain") - - // A second tick with no further restore must NOT rebuild (generation resync). - require.NoError(t, idx.processTick(ctx)) - cursor, err = rs.ReadAuditProgress() - require.NoError(t, err) - require.Equal(t, uint64(10), cursor, "no rebuild without a new restore") -} diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index a3d90b8261..c1c451ec82 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -77,15 +77,6 @@ type Builder struct { // resampled on each tick for the lag gauge. pebbleLastAuditSeq atomic.Uint64 - // restoreGen is the store restore generation this builder last processed - // under (see dal.Store.RestoreGeneration). Seeded on boot, re-read every - // tick: a change means the primary store was rolled back beneath the cursor - // at runtime, so a reset+rebuild is forced regardless of where the audit - // head landed relative to the cursor — the position-based cursorAheadOfHead - // / gapExceedsThreshold signals can be erased by a catch-up race. Mirrors - // auditindexer.Indexer.restoreGen. - restoreGen atomic.Uint64 - tw *tailworker.TailWorker reg metric.Registration } @@ -173,10 +164,6 @@ func (b *Builder) PebbleLastAuditSequence() uint64 { // loop (returned to tailworker, which logs and stops); a catch-up error is // logged and swallowed so steady-state indexing still starts. func (b *Builder) boot(ctx context.Context) error { - // Snapshot the restore generation before any processing so tick() can detect - // a runtime rollback that lands after boot. - b.restoreGen.Store(b.pebbleStore.RestoreGeneration()) - cursor, err := b.usageStore.ReadProgress() if err != nil { return fmt.Errorf("reading usage progress: %w", err) @@ -259,23 +246,6 @@ func (b *Builder) boot(ctx context.Context) error { func (b *Builder) tick(ctx context.Context) error { cursor := b.lastProcessedAuditSeq.Load() - // Restore-generation change is the authoritative rollback signal: it fires - // on any RestoreCheckpoint regardless of where the audit head landed, so it - // catches the catch-up race that erases the cursor>head position signal - // (restore below cursor, head re-grows past the old cursor before this tick - // re-samples). Checked before the position heuristic and independent of the - // head sample. Mirrors auditindexer.processTick. - if gen := b.pebbleStore.RestoreGeneration(); gen != b.restoreGen.Load() { - b.logger.WithFields(map[string]any{"from": b.restoreGen.Load(), "to": gen}). - Infof("primary store restore detected (generation changed) — resetting usage projection and rebuilding from audit sequence 0") - - if err := b.resetProjection(); err != nil { - return err - } - - cursor = 0 - } - if last, err := b.sampleAuditHead(); err == nil { // Steady-state uses the cursor-ahead test ONLY (boot=false): the // over-threshold gap branch is a boot-only heuristic — applying it @@ -345,29 +315,13 @@ func (b *Builder) rewindOnRollback(cursor, head uint64, boot bool) (bool, error) "gapExceeded": gap, }).Infof("primary store appears rolled back — resetting usage projection and rebuilding from audit sequence 0") - if err := b.resetProjection(); err != nil { - return false, err - } - - return true, nil -} - -// resetProjection wipes the usagestore and rewinds the cursor to 0, re-syncing -// the restore generation up-front so a RestoreCheckpoint that lands during the -// subsequent replay bumps past this value and is re-detected on the next tick -// (rather than being silently swallowed). Shared by the generation-change and -// position-based rewind paths (DRY). Mirrors auditindexer.Rebuild's generation -// re-sync. -func (b *Builder) resetProjection() error { - b.restoreGen.Store(b.pebbleStore.RestoreGeneration()) - if err := b.usageStore.Reset(); err != nil { - return fmt.Errorf("resetting usage store after primary-store rollback: %w", err) + return false, fmt.Errorf("resetting usage store after primary-store rollback: %w", err) } b.lastProcessedAuditSeq.Store(0) - return nil + return true, nil } // sampleAuditHead opens a short-lived read handle to read the current audit diff --git a/internal/application/usagebuilder/builder_test.go b/internal/application/usagebuilder/builder_test.go index 4ebf2e0052..7724809f56 100644 --- a/internal/application/usagebuilder/builder_test.go +++ b/internal/application/usagebuilder/builder_test.go @@ -1,84 +1,25 @@ package usagebuilder import ( - "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/metric/noop" - "google.golang.org/protobuf/proto" logging "github.com/formancehq/go-libs/v5/pkg/observe/log" - "github.com/formancehq/ledger/v3/internal/proto/auditpb" "github.com/formancehq/ledger/v3/internal/proto/commonpb" - "github.com/formancehq/ledger/v3/internal/storage/dal" "github.com/formancehq/ledger/v3/internal/storage/usagestore" ) func newBuilderWithUsageStore(t *testing.T) (*Builder, *usagestore.Store) { t.Helper() - // Back the builder with a real (empty) primary store so the shared reset - // path (resetProjection → RestoreGeneration) is exercised without a nil - // dereference. The position-based rewind tests pass (cursor, head) directly, - // so the empty store's audit head is never sampled. - b, _, us := newBuilderWithStores(t) - - return b, us -} - -// newBuilderWithStores wires a Builder over a real primary dal.Store and a real -// usagestore, so tests can exercise the restore-generation path (which reads -// pebbleStore.RestoreGeneration and samples the audit head). -func newBuilderWithStores(t *testing.T) (*Builder, *dal.Store, *usagestore.Store) { - t.Helper() - - logger := logging.NopZap() - meter := noop.NewMeterProvider().Meter("test") - - main, err := dal.NewStore(t.TempDir(), logger, meter, dal.DefaultConfig()) - require.NoError(t, err) - t.Cleanup(func() { _ = main.Close() }) - - us, err := usagestore.New(t.TempDir(), logger, usagestore.DefaultConfig()) + us, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) require.NoError(t, err) t.Cleanup(func() { _ = us.Close() }) - return &Builder{ - pebbleStore: main, - usageStore: us, - logger: logger, - batchSize: DefaultBatchSize, - }, main, us -} - -// writeAuditEntry appends a minimal failed audit entry so the audit head -// advances without an AppliedProposal / items projection: the usagebuilder -// skips failed proposals (no state delta), which is all this test needs to -// drive the cursor forward past the generation-triggered reset. -func writeAuditEntry(t *testing.T, store *dal.Store, seq uint64) { - t.Helper() - - entry := &auditpb.AuditEntry{ - Sequence: seq, - ProposalId: seq, - Timestamp: &commonpb.Timestamp{Data: seq * 1_000_000}, - Outcome: &auditpb.AuditEntry_Failure{Failure: &auditpb.AuditFailure{}}, - Ledgers: []string{"l1"}, - } - - val, err := proto.Marshal(entry) - require.NoError(t, err) - - batch := store.OpenWriteSession() - kb := dal.NewKeyBuilder() - require.NoError(t, batch.SetBytes( - kb.PutZonePrefix(dal.ZoneCold, dal.SubColdAudit).PutUint64(seq).Build(), - val, - )) - require.NoError(t, batch.Commit()) + return &Builder{usageStore: us, logger: logging.NopZap()}, us } // TestRewindIfCursorAhead_RuntimeRestore is the regression guard for the @@ -208,65 +149,3 @@ func TestRewindOnRollback_BootGapThreshold(t *testing.T) { require.NoError(t, err) assert.False(t, rewound, "gap within threshold must not trigger a rewind") } - -// TestTickResetsOnRestoreGenerationChange is the regression for the catch-up -// race the cursor>head signal cannot catch: the primary store is restored -// beneath the usage cursor, then the audit head re-grows PAST the old cursor -// before tick() re-samples — so cursorAheadOfHead is false. The restore- -// generation change is the only remaining rollback signal, and tick() must -// reset the projection and rebuild from 0. -func TestTickResetsOnRestoreGenerationChange(t *testing.T) { - t.Parallel() - - b, main, us := newBuilderWithStores(t) - ctx := context.Background() - - // Seed the surviving audit chain (1..3) into the primary store and take a - // checkpoint — this is the state a runtime restore will roll back to. - for s := uint64(1); s <= 3; s++ { - writeAuditEntry(t, main, s) - } - checkpointID, err := main.CreateSnapshot() - require.NoError(t, err) - - // A projection that had already consumed up to audit seq 3, with stale rows. - batch := us.NewBatch() - require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) - require.NoError(t, us.WriteProgress(batch, 3)) - require.NoError(t, batch.Commit()) - - // boot seeds the generation baseline; head (3) == cursor (3), so no rewind. - require.NoError(t, b.boot(ctx)) - require.Equal(t, uint64(3), b.lastProcessedAuditSeq.Load()) - - // Runtime rollback: a real RestoreCheckpoint bumps the store generation, and - // the post-restore head re-grows to 5 — strictly ABOVE the stale cursor (3), - // so cursorAheadOfHead is inert and only the generation change can fire. - require.NoError(t, main.RestoreCheckpoint(checkpointID)) - for s := uint64(4); s <= 5; s++ { - writeAuditEntry(t, main, s) - } - - head, err := b.sampleAuditHead() - require.NoError(t, err) - require.Equal(t, uint64(5), head) - require.False(t, cursorAheadOfHead(3, head), "head re-grew past cursor: position signal is inert") - - // tick() must reset on the generation change and re-drain the surviving - // chain from 0, landing the cursor back at the head. - require.NoError(t, b.tick(ctx)) - - seq, err := us.ReadProgress() - require.NoError(t, err) - require.Equal(t, uint64(5), seq, "reset+rebuild must re-consume the full surviving chain") - - c, err := us.GetCounter("l1", usagestore.CounterPosting) - require.NoError(t, err) - assert.Equal(t, uint64(0), c, "stale counter must be wiped by the reset") - - // A second tick with no further restore must NOT reset (generation resync). - require.NoError(t, b.tick(ctx)) - seq, err = us.ReadProgress() - require.NoError(t, err) - assert.Equal(t, uint64(5), seq, "no reset without a new restore") -} diff --git a/internal/storage/dal/store.go b/internal/storage/dal/store.go index f21f41fdda..99cb5f1b8b 100644 --- a/internal/storage/dal/store.go +++ b/internal/storage/dal/store.go @@ -11,7 +11,6 @@ import ( "runtime" "strconv" "sync" - "sync/atomic" "time" "github.com/cockroachdb/pebble/v2" @@ -109,20 +108,6 @@ type Store struct { maxCheckpoints int stallState *WriteStallState iopsCounters *IOPSCounters - - // restoreGeneration is a process-local monotonic counter bumped on every - // successful RestoreCheckpoint. It lets audit-chain pollers (auditindexer, - // usagebuilder) detect that the primary store was rolled back beneath their - // cursor WITHOUT relying on the cursor>head position signal, which a - // catch-up race can erase (restore below cursor, then the head re-grows past - // the old cursor before the poller re-samples). A poller records the value - // it observed and forces a reset+rebuild the moment it changes. It is - // in-memory by design: RestoreCheckpoint only runs at runtime (follower sync - // via SynchronizeWithLeader), where the pollers keep running across the - // restore (StopBackgroundTasks does not stop them); a process restart resets - // both the store and the poller to 0, falling back to the boot-time - // cursor-ahead / gap heuristic that covers offline backup restores. - restoreGeneration atomic.Uint64 } // getDB returns the current pebble.DB. @@ -1414,12 +1399,6 @@ func (s *Store) RestoreCheckpoint(checkpointID uint64) error { s.currentCheckPoint = checkpointID s.oldestCheckpoint = checkpointID - // Bump the restore generation so audit-chain pollers detect this rollback - // regardless of where the audit head lands relative to their cursor. Done - // under dbMu.Lock (still held via the deferred unlock) so it is ordered - // after the publish; the atomic lets lock-free pollers read it. - s.restoreGeneration.Add(1) - s.logger.WithFields(map[string]any{ "checkpointId": checkpointID, }).Infof("Database restored from checkpoint") @@ -1427,16 +1406,6 @@ func (s *Store) RestoreCheckpoint(checkpointID uint64) error { return nil } -// RestoreGeneration returns the process-local count of successful -// RestoreCheckpoint calls. Audit-chain pollers (auditindexer, usagebuilder) -// snapshot this at boot and compare on every tick: a change means the primary -// store was rolled back beneath their cursor, so they must reset+rebuild even -// when the cursor>head position signal was erased by a catch-up race. See the -// restoreGeneration field comment. -func (s *Store) RestoreGeneration() uint64 { - return s.restoreGeneration.Load() -} - // IterateColdKVPairs iterates every cold-storable KV pair belonging to a // chapter via efficient prefixed range scans. // diff --git a/internal/storage/dal/store_restore_rollback_test.go b/internal/storage/dal/store_restore_rollback_test.go index c684718ee0..82401c23a9 100644 --- a/internal/storage/dal/store_restore_rollback_test.go +++ b/internal/storage/dal/store_restore_rollback_test.go @@ -245,36 +245,3 @@ func TestReconcileLiveAfterRestore_CleansUpWhenBothExist(t *testing.T) { _, err = os.Stat(discardDirectory) require.True(t, os.IsNotExist(err), "reconciliation must drop the stale live.discard") } - -// TestRestoreGenerationIncrementsOnSuccessfulRestore is the store-side contract -// the audit-chain pollers (auditindexer, usagebuilder) depend on to detect a -// runtime primary-store rollback that the cursor>head position signal can miss: -// a successful RestoreCheckpoint must bump RestoreGeneration, and a failed one -// must not. -func TestRestoreGenerationIncrementsOnSuccessfulRestore(t *testing.T) { - t.Parallel() - - s := newTestStore(t) - - require.Equal(t, uint64(0), s.RestoreGeneration(), "generation starts at 0") - - // Seed data and take a checkpoint. - batch := s.OpenWriteSession() - require.NoError(t, batch.SetBytes([]byte("k"), []byte("v"))) - require.NoError(t, batch.Commit()) - - checkpointID, err := s.CreateSnapshot() - require.NoError(t, err) - - // A successful restore bumps the generation. - require.NoError(t, s.RestoreCheckpoint(checkpointID)) - require.Equal(t, uint64(1), s.RestoreGeneration(), "successful restore must bump generation") - - // A failed restore (unknown checkpoint) must NOT bump the generation. - require.Error(t, s.RestoreCheckpoint(999_999)) - require.Equal(t, uint64(1), s.RestoreGeneration(), "failed restore must not bump generation") - - // A second successful restore bumps again (monotonic). - require.NoError(t, s.RestoreCheckpoint(checkpointID)) - require.Equal(t, uint64(2), s.RestoreGeneration(), "each successful restore bumps monotonically") -} From b09f82457718f4d27e0649da40635a88fea347e5 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Sun, 12 Jul 2026 22:58:44 +0200 Subject: [PATCH 25/35] refactor(EN-1334): drop impossible primary-store-rollback detection from usagebuilder The audit chain is append-only and monotone: writeAuditEntry runs only in Machine.applyProposal, which the applier feeds exclusively from committed Raft entries (node.go: rd.CommittedEntries). By Raft leader-completeness a committed entry survives every leadership change, and RestoreCheckpoint only ever advances a node that is behind (IsStoreUpToDate gates sync on LastAppliedIndex >= SnapshotIndex). The persisted usage cursor therefore can never legitimately sit ahead of the audit head, so cursorAheadOfHead is unreachable and the head-cursor gap heuristic only ever fires on normal lag (where a rebuild-from-0 is strictly more work than incremental catch-up). Removes rewindOnRollback, cursorAheadOfHead, gapExceedsThreshold, the rebuildThreshold field/param, and the now-obsolete rollback tests. boot()/tick() simply sample the head for the lag gauge and process forward. Companion PR removes the identical dead detection from auditindexer. --- cmd/ledgerctl/store/rebuild_usage.go | 4 +- internal/application/usagebuilder/builder.go | 117 ++------------ .../application/usagebuilder/builder_test.go | 151 ------------------ internal/bootstrap/module.go | 2 +- 4 files changed, 15 insertions(+), 259 deletions(-) delete mode 100644 internal/application/usagebuilder/builder_test.go diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go index 6d29dbb6f4..e8ce65f1b1 100644 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ b/cmd/ledgerctl/store/rebuild_usage.go @@ -215,9 +215,7 @@ func runRebuildUsage(cmd *cobra.Command, _ []string) error { return cmdutil.Displayed(err) } - // rebuildThreshold is irrelevant offline: RebuildAll replays from cursor 0 - // on a freshly-dropped store, so the boot gap heuristic never applies. - builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize, 0) + builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize) lastSeq, err := builder.RebuildAll() if err != nil { diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index c1c451ec82..00f8cf73ab 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -61,14 +61,6 @@ type Builder struct { batchSize int - // rebuildThreshold triggers a boot-time reset+replay when the audit head - // leads the persisted cursor by more than this many entries (0 disables - // the gap heuristic). Mirrors auditindexer.Config.RebuildThreshold — it is - // the secondary net that catches a primary-store rollback whose restored - // gap re-grew past the cursor before boot sampled the head, so the momentary - // cursor>head signature was never observable. See rewindIfCursorAhead. - rebuildThreshold uint64 - // lastProcessedAuditSeq mirrors usagestore.ReadProgress() and is // updated on every successful commit — the atomic hint lets external // readers (metrics, tests) avoid a Pebble Get. @@ -91,20 +83,18 @@ func NewBuilder( logger logging.Logger, meter metric.Meter, batchSize int, - rebuildThreshold uint64, ) *Builder { if batchSize <= 0 { batchSize = DefaultBatchSize } return &Builder{ - pebbleStore: pebbleStore, - usageStore: usageStore, - notifications: notifications, - logger: logger.WithFields(map[string]any{"cmp": "usage-builder"}), - meter: meter, - batchSize: batchSize, - rebuildThreshold: rebuildThreshold, + pebbleStore: pebbleStore, + usageStore: usageStore, + notifications: notifications, + logger: logger.WithFields(map[string]any{"cmp": "usage-builder"}), + meter: meter, + batchSize: batchSize, } } @@ -173,18 +163,6 @@ func (b *Builder) boot(ctx context.Context) error { pebbleLast, sampleErr := b.sampleAuditHead() if sampleErr == nil { - // Boot uses the full rewind test: cursor-ahead OR an over-threshold - // gap. The gap branch is the auditindexer-parity net for a rollback - // whose restored gap re-grew past the cursor before we sampled the - // head, so cursor>head was never observable. - rewound, err := b.rewindOnRollback(cursor, pebbleLast, true) - if err != nil { - return err - } - if rewound { - cursor = 0 - } - b.pebbleLastAuditSeq.Store(pebbleLast) } @@ -236,29 +214,17 @@ func (b *Builder) boot(ctx context.Context) error { // tick runs one steady-state iteration: refresh the audit-head gauge, then // drain any pending audit entries from the persisted cursor forward. // -// The cursor-ahead re-check is what catches a runtime primary-store restore -// (follower sync via SynchronizeWithLeader/RestoreCheckpoint) that drops the -// audit head below the persisted cursor WHILE this loop is already running. -// The boot() guard runs only once before the ticker, so without re-evaluating -// here processAuditEntries would resume from the stale in-memory cursor and -// silently skip every entry in the rolled-back gap forever. Same self-healing -// rewind as boot(). Mirrors auditindexer.processTick. +// No rollback/cursor-ahead re-check is needed: the audit chain is append-only +// and monotone. Audit entries are written only for committed Raft entries +// (Machine.applyProposal), so by Raft leader-completeness a written entry +// survives every leadership change and RestoreCheckpoint only ever advances a +// node that is behind (IsStoreUpToDate gates sync on LastAppliedIndex >= +// SnapshotIndex). The persisted cursor can therefore never legitimately sit +// ahead of the audit head, so there is nothing to rewind. func (b *Builder) tick(ctx context.Context) error { cursor := b.lastProcessedAuditSeq.Load() if last, err := b.sampleAuditHead(); err == nil { - // Steady-state uses the cursor-ahead test ONLY (boot=false): the - // over-threshold gap branch is a boot-only heuristic — applying it - // per tick would spuriously rebuild whenever a normal burst outpaces - // the drain between ticks. Same split as auditindexer.processTick. - rewound, rewindErr := b.rewindOnRollback(cursor, last, false) - if rewindErr != nil { - return rewindErr - } - if rewound { - cursor = 0 - } - b.pebbleLastAuditSeq.Store(last) } @@ -267,63 +233,6 @@ func (b *Builder) tick(ctx context.Context) error { return err } -// cursorAheadOfHead reports the post-restore rollback signature: the persisted -// usage cursor sits beyond the current audit head. Only possible after the -// primary Pebble store was restored/truncated to an earlier head while the -// usagestore directory was retained (boot after a backup restore, or a runtime -// follower sync via SynchronizeWithLeader/RestoreCheckpoint). -func cursorAheadOfHead(cursor, head uint64) bool { return cursor > head } - -// gapExceedsThreshold reports the auditindexer-parity secondary net: the audit -// head leads the cursor by more than rebuildThreshold entries. This catches a -// primary-store rollback whose restored gap re-grew past the cursor before the -// head was sampled — so cursorAheadOfHead never fired — by observing the -// resulting anomalous head−cursor gap instead. Mirrors the RebuildThreshold -// branch of auditindexer.shouldRebuildOnBoot. Boot-only (see rewindOnRollback). -func (b *Builder) gapExceedsThreshold(cursor, head uint64) bool { - return b.rebuildThreshold > 0 && head > cursor && head-cursor > b.rebuildThreshold -} - -// rewindOnRollback resets the usage projection and rewinds the cursor to 0 when -// a primary-store rollback is detected. The retained counter / template rows -// reflect audit entries that no longer exist, so a clean in-place rebuild is the -// only way to reconverge. Merely failing loud is not enough: once new activity -// grows the head back past the stale cursor the cursor-ahead signature -// disappears and the rolled-back gap would be skipped forever. -// -// Two signatures, mirroring auditindexer: -// - cursorAheadOfHead (both boot and tick): the direct rollback signature. -// - gapExceedsThreshold (boot only): the secondary net for a rollback whose -// restored gap re-grew past the cursor before the head was sampled. It is -// boot-only because per-tick it would spuriously rebuild whenever a normal -// ingest burst outpaces the drain between ticks (see auditindexer.processTick). -// -// Returns true when a rewind was performed (caller must treat the cursor as 0). -// Shared by boot() and tick() so both entry points heal identically (DRY). -func (b *Builder) rewindOnRollback(cursor, head uint64, boot bool) (bool, error) { - ahead := cursorAheadOfHead(cursor, head) - gap := boot && b.gapExceedsThreshold(cursor, head) - - if !ahead && !gap { - return false, nil - } - - b.logger.WithFields(map[string]any{ - "cursor": cursor, - "head": head, - "cursorAhead": ahead, - "gapExceeded": gap, - }).Infof("primary store appears rolled back — resetting usage projection and rebuilding from audit sequence 0") - - if err := b.usageStore.Reset(); err != nil { - return false, fmt.Errorf("resetting usage store after primary-store rollback: %w", err) - } - - b.lastProcessedAuditSeq.Store(0) - - return true, nil -} - // sampleAuditHead opens a short-lived read handle to read the current audit // head. The handle is closed immediately so RestoreCheckpoint's write lock // is not blocked during idle ticks. diff --git a/internal/application/usagebuilder/builder_test.go b/internal/application/usagebuilder/builder_test.go deleted file mode 100644 index 7724809f56..0000000000 --- a/internal/application/usagebuilder/builder_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package usagebuilder - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - logging "github.com/formancehq/go-libs/v5/pkg/observe/log" - - "github.com/formancehq/ledger/v3/internal/proto/commonpb" - "github.com/formancehq/ledger/v3/internal/storage/usagestore" -) - -func newBuilderWithUsageStore(t *testing.T) (*Builder, *usagestore.Store) { - t.Helper() - - us, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) - require.NoError(t, err) - t.Cleanup(func() { _ = us.Close() }) - - return &Builder{usageStore: us, logger: logging.NopZap()}, us -} - -// TestRewindIfCursorAhead_RuntimeRestore is the regression guard for the -// follower-sync corruption window: a primary-store restore (RestoreCheckpoint) -// drops the audit head below the persisted cursor WHILE the builder is running. -// tick() re-evaluates the cursor-ahead signature each pass and must rewind — -// wiping the stale rows and resetting the cursor to 0 — so the rolled-back gap -// is re-processed rather than skipped forever. -func TestRewindIfCursorAhead_RuntimeRestore(t *testing.T) { - t.Parallel() - - b, us := newBuilderWithUsageStore(t) - - // Simulate a projection that had consumed up to audit seq 500: stale - // counter + template rows plus a cursor at 500. - batch := us.NewBatch() - require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) - require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterVolume, 7)) - require.NoError(t, us.PutTemplateUsage(batch, "l1", "t1", &commonpb.TemplateUsage{Count: 9})) - require.NoError(t, us.WriteProgress(batch, 500)) - require.NoError(t, batch.Commit()) - - b.lastProcessedAuditSeq.Store(500) - - // Primary store was restored to head 120 (< cursor 500). The steady-state - // (boot=false) path detects the direct cursor-ahead signature. - rewound, err := b.rewindOnRollback(500, 120, false) - require.NoError(t, err) - require.True(t, rewound, "cursor 500 ahead of head 120 must trigger a rewind") - - // Projection wiped. - c, err := us.GetCounter("l1", usagestore.CounterPosting) - require.NoError(t, err) - assert.Equal(t, uint64(0), c, "stale posting counter must be wiped") - - v, err := us.GetCounter("l1", usagestore.CounterVolume) - require.NoError(t, err) - assert.Equal(t, uint64(0), v, "stale volume counter must be wiped") - - tu, err := us.GetTemplateUsage("l1", "t1") - require.NoError(t, err) - assert.Nil(t, tu, "stale template row must be wiped") - - // Persisted cursor and the in-memory hint both reset to 0 so catch-up - // re-processes the surviving audit chain from the start. - seq, err := us.ReadProgress() - require.NoError(t, err) - assert.Equal(t, uint64(0), seq, "persisted cursor must rewind to 0") - assert.Equal(t, uint64(0), b.lastProcessedAuditSeq.Load(), "in-memory cursor hint must rewind to 0") -} - -// TestRewindIfCursorAhead_SteadyStateNoOp confirms the common case — cursor at -// or behind the audit head — leaves the projection and cursor untouched. -func TestRewindIfCursorAhead_SteadyStateNoOp(t *testing.T) { - t.Parallel() - - b, us := newBuilderWithUsageStore(t) - - batch := us.NewBatch() - require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterPosting, 42)) - require.NoError(t, us.WriteProgress(batch, 100)) - require.NoError(t, batch.Commit()) - b.lastProcessedAuditSeq.Store(100) - - // Head 300 is ahead of cursor 100 — normal steady state, no rewind. No - // rebuildThreshold configured, so the gap heuristic is inert too. - rewound, err := b.rewindOnRollback(100, 300, false) - require.NoError(t, err) - require.False(t, rewound) - - c, err := us.GetCounter("l1", usagestore.CounterPosting) - require.NoError(t, err) - assert.Equal(t, uint64(42), c, "counter must survive when cursor is not ahead") - - seq, err := us.ReadProgress() - require.NoError(t, err) - assert.Equal(t, uint64(100), seq, "cursor must be untouched when not ahead") - - // Cursor exactly at head is also steady state (not ahead). - rewound, err = b.rewindOnRollback(300, 300, false) - require.NoError(t, err) - assert.False(t, rewound, "cursor == head is not ahead") -} - -// TestRewindOnRollback_BootGapThreshold is the auditindexer-parity regression: -// after an in-place primary-store restore, the restored gap can re-grow past -// the stale cursor before boot samples the head, so cursorAheadOfHead never -// fires. The boot-only gap heuristic (head−cursor > rebuildThreshold) is the -// secondary net that still triggers the reset/replay. -func TestRewindOnRollback_BootGapThreshold(t *testing.T) { - t.Parallel() - - b, us := newBuilderWithUsageStore(t) - b.rebuildThreshold = 1000 - - // Stale cursor at 500 with a leftover row; the restored-then-caught-up - // head is 5000 — head > cursor (so cursorAheadOfHead is FALSE), but the - // 4500-entry gap exceeds the threshold. - batch := us.NewBatch() - require.NoError(t, us.PutCounter(batch, "l1", usagestore.CounterVolume, 3)) - require.NoError(t, us.WriteProgress(batch, 500)) - require.NoError(t, batch.Commit()) - b.lastProcessedAuditSeq.Store(500) - - // Boot path (boot=true): the gap heuristic fires even though cursor Date: Mon, 13 Jul 2026 09:53:18 +0200 Subject: [PATCH 26/35] feat(EN-1334): remove offline usage-rebuild; defer correct rebuild to 3.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `ledgerctl store rebuild-usage` command replayed the audit chain from sequence 0 over the self-purging primary Pebble store. Once a chapter has been archived to cold storage, the volumes touched by archived entries are gone from the reachable audit, so the replay undercounts VolumeCount and produces a WRONG projection. Rather than ship a rebuild that lies, remove the offline rebuild-from-scratch capability for 3.0. A correct, archival-proof rebuild — seeding VolumeCount from live SubAttrVolume state instead of replaying archived history — is deferred to 3.1. Removed: - usagebuilder.Builder.RebuildAll (only caller was the CLI command) - cmd/ledgerctl/store/rebuild_usage.go + _test.go (the whole command) - its registration in cmd/ledgerctl/store/command.go - the rebuild-usage section in docs/ops/cli.md and every other doc/openapi reference The steady-state incremental fold (boot/tick -> processAuditEntries) and the online in-place reset (usagestore.Reset on rollback detection) are untouched: reconvergence in 3.0 relies exclusively on those. --- cmd/ledgerctl/store/command.go | 1 - cmd/ledgerctl/store/rebuild_usage.go | 242 ------------------ cmd/ledgerctl/store/rebuild_usage_test.go | 90 ------- docs/ops/cli.md | 36 --- .../architecture/subsystems/usage/counters.md | 2 +- .../subsystems/usage/usagebuilder.md | 13 +- docs/technical/contributing/api-comparison.md | 2 +- internal/application/usagebuilder/builder.go | 6 +- .../application/usagebuilder/process_audit.go | 11 +- internal/bootstrap/module.go | 4 +- internal/storage/usagestore/store.go | 2 +- openapi.yml | 3 +- 12 files changed, 17 insertions(+), 395 deletions(-) delete mode 100644 cmd/ledgerctl/store/rebuild_usage.go delete mode 100644 cmd/ledgerctl/store/rebuild_usage_test.go diff --git a/cmd/ledgerctl/store/command.go b/cmd/ledgerctl/store/command.go index 308d69801e..118f67cfd5 100644 --- a/cmd/ledgerctl/store/command.go +++ b/cmd/ledgerctl/store/command.go @@ -19,7 +19,6 @@ func NewCommand() *cobra.Command { cmd.AddCommand(NewBootstrapCommand()) cmd.AddCommand(NewRebuildIndexesCommand()) cmd.AddCommand(NewRebuildAuditIndexCommand()) - cmd.AddCommand(NewRebuildUsageCommand()) cmd.AddCommand(NewCheckpointCommand()) cmd.AddCommand(NewDumpCommand()) cmd.AddCommand(NewBackupCommand()) diff --git a/cmd/ledgerctl/store/rebuild_usage.go b/cmd/ledgerctl/store/rebuild_usage.go deleted file mode 100644 index e8ce65f1b1..0000000000 --- a/cmd/ledgerctl/store/rebuild_usage.go +++ /dev/null @@ -1,242 +0,0 @@ -package store - -import ( - "errors" - "fmt" - "io/fs" - "os" - "path/filepath" - "strings" - - "github.com/pterm/pterm" - "github.com/spf13/cobra" - "go.opentelemetry.io/otel/metric/noop" - - logging "github.com/formancehq/go-libs/v5/pkg/observe/log" - - "github.com/formancehq/ledger/v3/cmd/ledgerctl/cmdutil" - "github.com/formancehq/ledger/v3/internal/application/usagebuilder" - "github.com/formancehq/ledger/v3/internal/storage/dal" - "github.com/formancehq/ledger/v3/internal/storage/usagestore" -) - -// canonicalizeDir resolves p to its absolute, symlink-free form. If p (or -// any leading ancestor) does not exist yet — typical for --usage-dir on a -// first rebuild — the deepest existing ancestor is resolved and the missing -// tail re-appended, so a symlinked parent still normalises before we do -// prefix comparisons in ensureDisjointDirs. -func canonicalizeDir(p string) (string, error) { - abs, err := filepath.Abs(p) - if err != nil { - return "", fmt.Errorf("resolving absolute path for %q: %w", p, err) - } - - curr := abs - tail := "" - - for { - resolved, err := filepath.EvalSymlinks(curr) - if err == nil { - if tail == "" { - return resolved, nil - } - - return filepath.Join(resolved, tail), nil - } - if !errors.Is(err, fs.ErrNotExist) { - return "", fmt.Errorf("resolving symlinks for %q: %w", curr, err) - } - - parent := filepath.Dir(curr) - if parent == curr { - // Reached the filesystem root without finding an existing - // ancestor. Fall back to the unresolved absolute path. - return abs, nil - } - tail = filepath.Join(filepath.Base(curr), tail) - curr = parent - } -} - -// ensureDisjointDirs rejects any --usage-dir value that would overlap the -// primary Pebble store — the rebuild command RemoveAlls the usage dir before -// re-opening the data dir, so a colliding path silently wipes production -// data. -// -// The four rejected shapes on canonicalised paths (symlinks resolved so an -// operator cannot bypass the check by pointing --usage-dir at a symlinked -// alias of the primary store): -// - usageDir == dataDir (obvious: wipes the whole data root) -// - usageDir is a parent of dataDir (wipes the whole data root) -// - usageDir == /live (wipes Pebble's actual live directory) -// - usageDir is a parent or child of /live (wipes Pebble too) -// -// The documented default is `/usage`, which is a sibling of -// `/live` and therefore safe. -func ensureDisjointDirs(dataDir, usageDir string) error { - absData, err := canonicalizeDir(dataDir) - if err != nil { - return fmt.Errorf("resolving --data-dir: %w", err) - } - absUsage, err := canonicalizeDir(usageDir) - if err != nil { - return fmt.Errorf("resolving --usage-dir: %w", err) - } - absLive, err := canonicalizeDir(filepath.Join(absData, "live")) - if err != nil { - return fmt.Errorf("resolving --data-dir/live: %w", err) - } - - if absData == absUsage { - return fmt.Errorf("--usage-dir (%s) must not equal --data-dir — running this command would delete the primary Pebble store", absUsage) - } - if strings.HasPrefix(absData+string(filepath.Separator), absUsage+string(filepath.Separator)) { - return fmt.Errorf("--usage-dir (%s) must not be a parent of --data-dir (%s) — running this command would delete the primary Pebble store", absUsage, absData) - } - if absUsage == absLive { - return fmt.Errorf("--usage-dir (%s) must not equal the primary Pebble live directory — running this command would delete it", absUsage) - } - if strings.HasPrefix(absLive+string(filepath.Separator), absUsage+string(filepath.Separator)) { - return fmt.Errorf("--usage-dir (%s) must not be a parent of the primary Pebble live directory (%s) — running this command would delete it", absUsage, absLive) - } - if strings.HasPrefix(absUsage+string(filepath.Separator), absLive+string(filepath.Separator)) { - return fmt.Errorf("--usage-dir (%s) must not live inside the primary Pebble directory (%s) — running this command would delete Pebble state", absUsage, absLive) - } - - return nil -} - -// NewRebuildUsageCommand creates the store rebuild-usage command. -func NewRebuildUsageCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "rebuild-usage", - Short: "Rebuild the usage store from the audit chain (offline)", - Long: `Drop the usage store directory and replay every audit entry from -sequence 0, rebuilding all per-template usage counters and per-ledger event -counters from scratch. This is a purely offline operation — the server must -be stopped. - -Use this after restoring from a backup, when the usage store becomes -corrupted, or when audit-chain history has been extended and you want the -counters to reflect the full history that is still available in Pebble. - -Note that audit entries archived to cold storage are not reconstructed — -the rebuild only replays what is currently reachable in the primary Pebble -store.`, - RunE: runRebuildUsage, - Args: cobra.ExactArgs(0), - ValidArgsFunction: cobra.NoFileCompletions, - } - - cmd.Flags().String("data-dir", "", "Pebble data directory (required)") - cmd.Flags().String("usage-dir", "", "Usage store output directory (default: /usage/)") - cmd.Flags().Int("usage-batch-size", 0, "Number of audit entries per Pebble batch commit (0 = default 200)") - - _ = cmd.MarkFlagRequired("data-dir") - - return cmd -} - -func runRebuildUsage(cmd *cobra.Command, _ []string) error { - var ( - dataDir, _ = cmd.Flags().GetString("data-dir") - usageDir, _ = cmd.Flags().GetString("usage-dir") - batchSize, _ = cmd.Flags().GetInt("usage-batch-size") - ) - - if usageDir == "" { - usageDir = filepath.Join(dataDir, "usage") - } - - // Guard against an operator passing --usage-dir equal to or inside - // --data-dir: the RemoveAll below would then wipe (part of) the live - // Pebble store before we ever open it read-only. Same category of - // footgun as running `rm -rf` on a mount point. - if err := ensureDisjointDirs(dataDir, usageDir); err != nil { - return cmdutil.Displayed(err) - } - - logger := logging.NopZap() - - // Drop the existing usage store so the builder starts at cursor=0 and - // no stale counter survives the rebuild. - spinner, err := startSpinner("Dropping existing usage store...") - if err != nil { - return cmdutil.Displayed(err) - } - - if err := os.RemoveAll(usageDir); err != nil { - spinner.Fail("Failed to drop usage store directory") - - return cmdutil.Displayed(fmt.Errorf("removing %s: %w", usageDir, err)) - } - - spinner.Success("Usage store dropped at " + usageDir) - - // Open primary Pebble read-only — same as rebuild-indexes / - // rebuild-audit-index (the live SSTs are at /live, not - // dataDir itself). - spinner, err = startSpinner("Opening Pebble store (read-only)...") - if err != nil { - return cmdutil.Displayed(err) - } - - pebbleStore, err := dal.OpenReadOnly(filepath.Join(dataDir, "live"), logger) - if err != nil { - spinner.Fail("Failed to open Pebble store") - - return cmdutil.Displayed(fmt.Errorf("opening Pebble store: %w", err)) - } - - defer func() { _ = pebbleStore.Close() }() - - spinner.Success("Pebble store opened") - - // Create the fresh usage store. - spinner, err = startSpinner("Creating usage store...") - if err != nil { - return cmdutil.Displayed(err) - } - - us, err := usagestore.New(usageDir, logger, usagestore.DefaultConfig()) - if err != nil { - spinner.Fail("Failed to create usage store") - - return cmdutil.Displayed(fmt.Errorf("creating usage store: %w", err)) - } - - defer func() { _ = us.Close() }() - - spinner.Success("Usage store created at " + us.Path()) - - // Rebuild — notifications is nil in offline mode (no FSM running). - spinner, err = startSpinner("Rebuilding usage projections from the audit chain...") - if err != nil { - return cmdutil.Displayed(err) - } - - builder := usagebuilder.NewBuilder(pebbleStore, us, nil, logger, noop.Meter{}, batchSize) - - lastSeq, err := builder.RebuildAll() - if err != nil { - spinner.Fail("Rebuild failed") - - return cmdutil.Displayed(fmt.Errorf("rebuilding usage projections: %w", err)) - } - - spinner.Success(fmt.Sprintf("Rebuild complete (last audit sequence: %d)", lastSeq)) - - return nil -} - -// startSpinner starts a live-renderer spinner and propagates any -// initialization error instead of discarding it — a failed Start returns a nil -// *SpinnerPrinter, so dereferencing it (Fail/Success) would panic. -func startSpinner(text string) (*pterm.SpinnerPrinter, error) { - spinner, err := pterm.DefaultSpinner.Start(text) - if err != nil { - return nil, fmt.Errorf("starting spinner: %w", err) - } - - return spinner, nil -} diff --git a/cmd/ledgerctl/store/rebuild_usage_test.go b/cmd/ledgerctl/store/rebuild_usage_test.go deleted file mode 100644 index 71fb640cd1..0000000000 --- a/cmd/ledgerctl/store/rebuild_usage_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package store - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -// TestEnsureDisjointDirs_Symlink verifies the guard resolves symlinks — -// filepath.Abs alone would let a symlink pointing at /live slip -// past the prefix check and get RemoveAll'd. -func TestEnsureDisjointDirs_Symlink(t *testing.T) { - t.Parallel() - - root := t.TempDir() - dataDir := filepath.Join(root, "data") - liveDir := filepath.Join(dataDir, "live") - require.NoError(t, os.MkdirAll(liveDir, 0o755)) - - // symlink-to-live: usage-dir is a symbolic link whose target is the - // primary Pebble live directory. Must be rejected even though the - // literal path string differs. - symlink := filepath.Join(root, "usage-symlink") - require.NoError(t, os.Symlink(liveDir, symlink)) - - err := ensureDisjointDirs(dataDir, symlink) - require.Error(t, err) - require.Contains(t, err.Error(), "must not equal the primary Pebble live directory") -} - -// TestEnsureDisjointDirs exercises the guards that prevent `--usage-dir` -// from silently wiping the primary Pebble store. The command RemoveAlls the -// usage dir before opening data, so any overlap is destructive. -func TestEnsureDisjointDirs(t *testing.T) { - t.Parallel() - - root := t.TempDir() - dataDir := filepath.Join(root, "data") - liveDir := filepath.Join(dataDir, "live") - - tests := []struct { - name string - usage string - wantErr string - }{ - { - name: "sibling under data-dir (default)", - usage: filepath.Join(dataDir, "usage"), - }, - { - name: "fully separate root", - usage: filepath.Join(root, "elsewhere", "usage"), - }, - { - name: "equal to data-dir", - usage: dataDir, - wantErr: "must not equal --data-dir", - }, - { - name: "parent of data-dir", - usage: root, - wantErr: "must not be a parent of --data-dir", - }, - { - name: "equal to /live", - usage: liveDir, - wantErr: "must not equal the primary Pebble live directory", - }, - { - name: "inside /live", - usage: filepath.Join(liveDir, "nested"), - wantErr: "must not live inside the primary Pebble directory", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - err := ensureDisjointDirs(dataDir, tc.usage) - if tc.wantErr == "" { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.wantErr) - } - }) - } -} diff --git a/docs/ops/cli.md b/docs/ops/cli.md index e96f79c688..6dd5bf2331 100644 --- a/docs/ops/cli.md +++ b/docs/ops/cli.md @@ -2219,42 +2219,6 @@ ledgerctl store rebuild-audit-index --data-dir ./data --read-index-dir ./custom- --- -### store rebuild-usage - -Drop the usage store directory and replay every audit entry from sequence 0 to rebuild all per-template invocation counters and per-ledger event counters from scratch. Purely offline — the server must be stopped. - -Use this after restoring from a backup, when the usage store becomes corrupted, or when a schema change requires the counters to be re-derived. - -```bash -ledgerctl store rebuild-usage --data-dir /path/to/data [flags] -``` - -**Flags:** - -| Flag | Default | Description | -|------|---------|-------------| -| `--data-dir` | | Pebble data directory (required) | -| `--usage-dir` | | Usage store output directory (default: `/usage/`) | -| `--usage-batch-size` | `0` | Audit entries per Pebble batch commit (`0` = default 200) | - -**Behavior:** - -1. Removes the existing usage store directory (dropping every counter and cursor). -2. Opens the primary Pebble data directory in read-only mode. -3. Creates a fresh usage store. -4. Replays every audit entry reachable in Pebble, materialising per-template usage records and per-ledger event counters. -5. Reports the last processed audit sequence on completion. - -Note that audit entries archived to cold storage are not reconstructed — the rebuild only replays what is currently reachable in the primary Pebble store. Counters end up reflecting invocations from the earliest reachable audit entry forward. - -**Example:** - -```bash -ledgerctl store rebuild-usage --data-dir ./data -``` - ---- - ### audit View the replicated audit log. The audit log captures every proposal (success and failure) that goes through Raft consensus, providing a complete audit trail. diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md index fde7bb06e2..832c10d05f 100644 --- a/docs/technical/architecture/subsystems/usage/counters.md +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -119,7 +119,7 @@ Full keyspace conventions in `internal/storage/usagestore/keys.go`. The `[ledger ### Why the usagestore counters are not a checker target (design decision, not a gap) -The checker (invariant #8) verifies projections persisted **in the primary Pebble store** — the store it operates on: `Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, idempotency outcomes, the index registry, and the exclusion projection above. The usagestore is a **distinct, peer secondary Pebble instance** (`/usage/`) holding a *derived cache* of counters, fully rebuildable from the audit chain on demand (`ledgerctl store rebuild-usage`, or the automatic boot/tick rewind). Its authoritativeness is explicitly bounded by "eventually consistent with the FSM". +The checker (invariant #8) verifies projections persisted **in the primary Pebble store** — the store it operates on: `Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, idempotency outcomes, the index registry, and the exclusion projection above. The usagestore is a **distinct, peer secondary Pebble instance** (`/usage/`) holding a *derived cache* of counters, rebuildable from the audit chain via the automatic boot/tick fold (and `usagestore.Reset()` on rollback detection). Its authoritativeness is explicitly bounded by "eventually consistent with the FSM". Consequently, the three volume-annotation categories are deliberately **not** given a dedicated usagestore checker pass: diff --git a/docs/technical/architecture/subsystems/usage/usagebuilder.md b/docs/technical/architecture/subsystems/usage/usagebuilder.md index 48ab58c0b2..e47a1fddd9 100644 --- a/docs/technical/architecture/subsystems/usage/usagebuilder.md +++ b/docs/technical/architecture/subsystems/usage/usagebuilder.md @@ -110,7 +110,7 @@ stats.PostingCount, _ = snap.GetCounter(ledger, usagestore.CounterPosting) | Usagebuilder crash mid-batch | Cursor at last successful commit; committed counter deltas are durable | Loop restarts, reads persisted cursor, resumes from `cursor + 1`. | | Cold-storage archival of an early chapter | Nothing lost — counters already applied by the usagebuilder are persisted in usagestore | Cursor stays past the archived range; no re-processing. | | Ledger deletion (audit log `DeleteLedger`) | Usagestore range-deletes every counter + template row keyed on the ledger via `DeleteLedger(batch, ledgerName)` | Re-created ledgers start at zero counters; audit-chain history for the old incarnation is idempotent to re-process. | -| Full rebuild via `ledgerctl store rebuild-usage` | Nothing — the usagestore directory is dropped | Cursor resets to 0; the builder replays the entire reachable audit chain into a fresh store. See [rebuild-usage](../../../ops/cli.md#store-rebuild-usage). | +| Primary store rolled back beneath the persisted cursor | Nothing — `usagestore.Reset()` drops every counter + template row and clears the cursor | The next boot/tick replays from audit sequence 0 into the freshly-reset store. | ## Cutover semantics @@ -118,16 +118,15 @@ The migration that introduced this subsystem (EN-1420 / EN-1422) moved every non ## Snapshot / restore -The usagestore is not part of Pebble snapshots or backups: it is a projection that is trivially rebuildable from the audit chain. On restore, the operator can either: +The usagestore is not part of Pebble snapshots or backups: it is a projection that is trivially rebuildable from the audit chain. On restore, the running usagebuilder catches up organically from wherever its persisted cursor points; if the primary store was rolled back beneath that cursor, `usagestore.Reset()` fires on the next boot/tick and the projection repopulates from audit sequence 0. -- Let the running usagebuilder catch up organically from wherever its persisted cursor points, or -- Run `ledgerctl store rebuild-usage` offline before restart to drop-and-repopulate. +The audit chain remains the source of truth in every case. -The audit chain remains the source of truth in both cases. +> **3.0 limitation.** There is no offline drop-and-rebuild-from-scratch command. The now-removed `ledgerctl store rebuild-usage` replayed the audit chain from sequence 0 over the self-purging primary store, which undercounts `VolumeCount` once a chapter has been archived (the volumes touched by archived entries are gone from the reachable audit). Rather than ship a rebuild that lies, offline rebuild is deferred to 3.1, where it will seed `VolumeCount` from live `SubAttrVolume` state instead of replaying archived history. In 3.0, reconvergence relies exclusively on the online boot/tick fold + `Reset()`. ## Integrity verification (checker scope) -The usagestore is **deliberately excluded from `internal/application/check/checker.go`**. Invariant #8 ("every persisted projection must be verified by the checker") is scoped to projections that live in the **primary** Pebble store — the store that participates in Pebble snapshots, backups and cold-storage, and that an operator cannot rebuild without stopping the cluster. The usagestore, like the read store (`readstore`), is a physically separate secondary Pebble instance at `/usage/`: it is never snapshotted, never backed up, and is trivially rebuildable offline via `ledgerctl store rebuild-usage` (drop the directory + replay from audit sequence 0). A tampered or corrupted usagestore is therefore not a durable integrity vector — the next rebuild reconstructs it from the hash-chained audit, which the checker *does* verify. Extending the checker to walk a peer store would couple it to a subsystem it has no authority over and duplicate the rebuild logic that already re-derives every counter from the same source of truth. +The usagestore is **deliberately excluded from `internal/application/check/checker.go`**. Invariant #8 ("every persisted projection must be verified by the checker") is scoped to projections that live in the **primary** Pebble store — the store that participates in Pebble snapshots, backups and cold-storage, and that an operator cannot rebuild without stopping the cluster. The usagestore, like the read store (`readstore`), is a physically separate secondary Pebble instance at `/usage/`: it is never snapshotted, never backed up, and is rebuildable from the audit chain via the online boot/tick fold (and `usagestore.Reset()` on rollback detection). A tampered or corrupted usagestore is therefore not a durable integrity vector — the next fold reconstructs it from the hash-chained audit, which the checker *does* verify. Extending the checker to walk a peer store would couple it to a subsystem it has no authority over and duplicate the rebuild logic that already re-derives every counter from the same source of truth. ## Summary @@ -140,4 +139,4 @@ The usagestore is **deliberately excluded from `internal/application/check/check | Read consistency | Multi-counter reads via `usagestore.NewSnapshot()` | `usagestore/snapshot.go`, `ctrl/controller_default.go` | | Isolation | Dedicated Pebble instance at `/usage/`, own comparer, WAL disabled | `usagestore/{store,comparer,keys}.go` | | Metrics | `tailworker.RegisterTailGauges` — 3 shared gauges (`last_indexed_sequence`, `audit_last_sequence`, `lag`) | `builder.go`, `internal/pkg/tailworker/gauges.go` | -| Rebuild | `ledgerctl store rebuild-usage` — drop directory + replay | `cmd/ledgerctl/store/rebuild_usage.go` | +| Rebuild | Online boot/tick fold from the persisted cursor; `usagestore.Reset()` drops rows + replays from 0 on rollback detection (offline rebuild-from-scratch deferred to 3.1 — see Snapshot / restore) | `builder.go`, `usagestore/store.go` | diff --git a/docs/technical/contributing/api-comparison.md b/docs/technical/contributing/api-comparison.md index d6360ece09..122f522ab7 100644 --- a/docs/technical/contributing/api-comparison.md +++ b/docs/technical/contributing/api-comparison.md @@ -379,7 +379,7 @@ A never-invoked template returns a zero-valued response (not 404), so clients ha - `count` (uint64): Number of times the template has been invoked. `0` means not yet invoked (or the usagebuilder has not caught up). - `lastUsed` (string, date-time, nullable): Timestamp of the most recent invocation. Absent when count is 0. -On a fresh ledger the counter builds up organically from cursor=0. On an existing ledger whose audit chain has been partially archived to cold storage, only invocations still present in the primary Pebble store are counted — use `ledgerctl store rebuild-usage` to replay from the reachable start. +On a fresh ledger the counter builds up organically from cursor=0. On an existing ledger whose audit chain has been partially archived to cold storage, only invocations still present in the primary Pebble store are counted. ### 10. Prepared Queries and User-Configurable Indexes diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 00f8cf73ab..3baa666495 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -106,9 +106,9 @@ func (b *Builder) Start() { b.reg = reg } - // Wake on FSM commit signals when available. `notifications` is nil - // only in the offline rebuild path (RebuildAll), which does not go - // through Start — the guard is defensive. + // Wake on FSM commit signals when available. The guard is defensive: + // steady-state always wires notifications, but a nil value must not + // panic (e.g. a builder constructed in a test without a live FSM). var wake <-chan struct{} if b.notifications != nil { wake = b.notifications.LogCommitted.C() diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 68c70085ce..903f58a74a 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -114,13 +114,6 @@ func (s *batchState) empty() bool { return len(s.counters) == 0 && len(s.templates) == 0 && len(s.deletedLedgers) == 0 } -// RebuildAll replays every audit entry from sequence 0, materialising the -// usagestore projections from scratch. Intended for offline use via -// `ledgerctl store rebuild-usage`. Returns the last processed audit sequence. -func (b *Builder) RebuildAll() (uint64, error) { - return b.processAuditEntries(context.Background(), 0, time.Time{}) -} - // processAuditEntries iterates audit entries after cursor, dispatches each // item, and commits per-ledger counter deltas + template usage updates // atomically alongside the cursor advance. Returns the new cursor. @@ -293,8 +286,8 @@ func (b *Builder) dispatchOrder( } // DeleteLedger orders MUST be projected — otherwise the usagestore - // keeps stale rows for the dropped ledger until an operator runs - // rebuild-usage. Same story as the readstore's DeleteLedgerIndexes. + // keeps stale rows for the dropped ledger indefinitely. Same story as + // the readstore's DeleteLedgerIndexes. if scoped.GetDeleteLedger() != nil { state.markLedgerDeleted(ledger) diff --git a/internal/bootstrap/module.go b/internal/bootstrap/module.go index 1782923c06..85d9025281 100644 --- a/internal/bootstrap/module.go +++ b/internal/bootstrap/module.go @@ -668,8 +668,8 @@ func Module() fx.Option { }, // Usage store (Pebble) — dedicated secondary store for the // usagebuilder projections. Kept physically separate from the - // readstore so operational rebuild (`ledgerctl store rebuild-usage`) - // is just a directory drop. + // readstore so an operational reset is just a directory drop and + // the builder re-derives every counter from the audit chain. func(cfg Config, logger logging.Logger) (*usagestore.Store, error) { dir := filepath.Join(cfg.DataDir, "usage") diff --git a/internal/storage/usagestore/store.go b/internal/storage/usagestore/store.go index b5b0f6f3ce..1a94c91d64 100644 --- a/internal/storage/usagestore/store.go +++ b/internal/storage/usagestore/store.go @@ -193,7 +193,7 @@ func (s *Store) WriteProgress(batch *dal.WriteSession, sequence uint64) error { // boot replays from audit sequence 0. Used when the builder detects that the // primary store was rolled back beneath the persisted cursor: the retained // rows reflect audit entries that no longer exist, so a clean in-place rebuild -// is the only way to reconverge without an operator running rebuild-usage. +// on the next boot/tick is how the projection reconverges. // // The two ledger-scoped prefixes (PrefixTemplate 0x01, PrefixCounter 0x02) are // contiguous, so one DeleteRange over [0x01, 0x03) covers both; the internal diff --git a/openapi.yml b/openapi.yml index 4e2d4575f6..b2f1f90540 100644 --- a/openapi.yml +++ b/openapi.yml @@ -1669,8 +1669,7 @@ paths: template returns a zero-valued response (not 404), so clients handle "never used" uniformly. On existing ledgers whose audit chain has been partially archived to cold storage, only - invocations still reachable in Pebble are counted. Use `ledgerctl store rebuild-usage` to - replay from the reachable start. + invocations still reachable in Pebble are counted. operationId: getNumscriptUsage tags: - Numscript Library From aefc0848658b7b0a2e9fee5a1d950868cdd33ae0 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 13 Jul 2026 10:08:06 +0200 Subject: [PATCH 27/35] test(EN-1334): wrap transient-purge log assertion in Eventually The 'Should record purged accounts on the logs that touched them' spec queried ListLogs (eventually-consistent read-side) once, with no retry, then asserted the log list was non-empty. On a fast runner the query lands before the read-side projection has indexed the batch, so logs came back empty and the spec flaked (green->FAILURE on Tests-E2E). Wrap the query and both assertions in Eventually (5s / 200ms), matching every other read-after-write assertion in this file. The Apply mutation stays outside the retry loop. --- tests/e2e/business/transient_accounts_test.go | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/tests/e2e/business/transient_accounts_test.go b/tests/e2e/business/transient_accounts_test.go index c46b913de5..06b9059983 100644 --- a/tests/e2e/business/transient_accounts_test.go +++ b/tests/e2e/business/transient_accounts_test.go @@ -139,40 +139,45 @@ var _ = Describe("TransientAccounts", Ordered, func() { }, nil))) Expect(err).To(Succeed()) - stream, err := sharedClient.ListLogs(sharedCtx, &servicepb.ListLogsRequest{ - Ledger: ledgerName, - }) - Expect(err).To(Succeed()) - - logs := collectLogs(stream) - Expect(logs).NotTo(BeEmpty()) - - // At least one of the two logs in the batch must carry - // {Account: clearing:ep1, Asset: USD} in its eviction lists. - // Pure ephemeral tuples (was zero, briefly touched, is zero) - // live in LedgerLog.EphemeralVolumes; draining evictions - // (was non-zero, back to zero) live in LedgerLog.PurgedVolumes. - // The index builder unions both to skip the matching - // account->transaction mapping while preserving any other - // asset's mappings on the same account. + // ListLogs is served from the eventually-consistent read-side, so + // retry until the batch's logs have been indexed (mirrors every + // other read-after-write assertion in this file). type touched struct{ Account, Asset string } - var purged []touched - for _, log := range logs { - apply := log.GetPayload().GetApply() - if apply == nil || apply.GetLedgerName() != ledgerName { - continue - } - if ll := apply.GetLog(); ll != nil { - for _, v := range ll.GetPurgedVolumes() { - purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) + Eventually(func(g Gomega) { + stream, err := sharedClient.ListLogs(sharedCtx, &servicepb.ListLogsRequest{ + Ledger: ledgerName, + }) + g.Expect(err).To(Succeed()) + + logs := collectLogs(stream) + g.Expect(logs).NotTo(BeEmpty()) + + // At least one of the two logs in the batch must carry + // {Account: clearing:ep1, Asset: USD} in its eviction lists. + // Pure ephemeral tuples (was zero, briefly touched, is zero) + // live in LedgerLog.EphemeralVolumes; draining evictions + // (was non-zero, back to zero) live in LedgerLog.PurgedVolumes. + // The index builder unions both to skip the matching + // account->transaction mapping while preserving any other + // asset's mappings on the same account. + var purged []touched + for _, log := range logs { + apply := log.GetPayload().GetApply() + if apply == nil || apply.GetLedgerName() != ledgerName { + continue } - for _, v := range ll.GetEphemeralVolumes() { - purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) + if ll := apply.GetLog(); ll != nil { + for _, v := range ll.GetPurgedVolumes() { + purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) + } + for _, v := range ll.GetEphemeralVolumes() { + purged = append(purged, touched{Account: v.GetAccount(), Asset: v.GetAsset()}) + } } } - } - Expect(purged).To(ContainElement(touched{Account: "clearing:ep1", Asset: "USD"}), - "at least one log in the batch should list (clearing:ep1, USD) as evicted") + g.Expect(purged).To(ContainElement(touched{Account: "clearing:ep1", Asset: "USD"}), + "at least one log in the batch should list (clearing:ep1, USD) as evicted") + }).Within(5 * time.Second).ProbeEvery(200 * time.Millisecond).Should(Succeed()) }) }) From fe3c88340dfeccf7bc06d6c867fe8b29b915dad0 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Mon, 13 Jul 2026 15:04:44 +0200 Subject: [PATCH 28/35] docs(EN-1334): fix usagestore rollback/checker-scope doc drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the watermark/rollback revert (b09f8245) the docs still described a rollback-rewind mechanism and a checker backstop that no longer exist: - AGENTS.md: the volume-preload invariant claimed 'the checker's volume-count pass would surface the drift'. usagestore is a rebuildable peer side-store outside checker scope (invariant #8 = primary-store projections only), so there is no such backstop — reworded to reflect that (which makes preload correctness more load-bearing, not less). - module.go: the NewBuilder comment claimed a shared 'rebuild threshold' + a rollback-then-catch-up gap heuristic. The final arg is batchSize (0=default) and there is no rollback guard (append-only audit). - usagestore/store.go: Reset() doc framed it as builder-triggered on rollback detection; it is a full-rebuild primitive not called for rollbacks. Also corrected the crash-safety note to rely on single-batch atomicity. - counters.md: dropped the 'automatic boot/tick rewind / Reset() on rollback detection' claim; the fold only moves forward from the cursor. Docs/comments only; build clean. --- AGENTS.md | 2 +- .../architecture/subsystems/usage/counters.md | 2 +- internal/bootstrap/module.go | 8 +++++--- internal/storage/usagestore/store.go | 16 ++++++++-------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e84547c0e9..0dbf3bd84a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ This document contains rules and conventions for AI agents working on this codeb 5. **Never delete cache entries outside of rotations** — Cache entries must only be evicted during generation rotations (Gen0 → Gen1 → discard). Deleting individual entries breaks the cache prediction mechanism (bloom filters, tombstones). 6. **Every FSM `Registry.X.Get(...)` must have a matching preload, declared by the component that proposes the command** — The FSM apply path reads from the in-memory cache; a cache miss turns the read into a silent no-op. Each component that emits a proposal (the metadata converter, the index builder, the cluster-config reconciler, the idempotency-eviction scheduler, the mirror worker, admission) is responsible for declaring its own `preload.Needs` covering every key its apply path will read. There is NO central proposal→needs registry — coupling the preload package to every proposal type creates a single point that easily falls behind. The component knows what it reads; the component declares it. The shared `proposeTechnical` helper takes a `*preload.Needs` parameter the caller fills in (pass nil or an empty `Needs` when the apply path has no cache-keyed reads, e.g. cluster config / idempotency eviction). The preload populates the cache via `MirrorPreload` with a fresh value read at propose time (Pebble fallback on cache miss), and `PredictedIndex` catches mutations between propose and apply. - **Volume preload is load-bearing for the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` split.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, mis-routes the tuple between the `new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` per-log lists, and inflates `usagestore.CounterVolume`. Only the checker's volume-count pass would eventually surface the drift. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. + **Volume preload is load-bearing for the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` split.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, mis-routes the tuple between the `new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` per-log lists, and inflates `usagestore.CounterVolume`. Because `usagestore` is a rebuildable peer side-store held **outside** checker scope (invariant #8 verifies only primary-Pebble-store projections), no checker pass would surface the drift; it persists until an explicit usage rebuild. That absence of a backstop makes the preload correctness even more load-bearing, not less. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. 7. **Never silently skip a "should not happen" branch** — A branch that is reachable only if an invariant is violated (nil where the contract says non-nil, a state we believe unreachable, a cache miss after a guaranteed preload, etc.) MUST surface a loud signal: `return fmt.Errorf("invariant: ...")` so it bubbles up, or `assert.Unreachable(...)` for SUT-level invariants exercised under antithesis. A silent `return nil` / `continue` on these branches hides real bugs — particularly catastrophic in the FSM apply path, where a no-op desyncs nodes from each other. Branches that represent genuine runtime conditions (cache miss as an expected outcome, stale proposal, deleted entity) keep their soft `return nil`. The distinction is whether the case is *expected* (soft skip OK) or *impossible by design* (must fail loudly). The comment must say *why* the case is impossible so a reader can decide whether to add a hard fail or relax the rule. 8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus); extend the list as new persisted projections land. diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md index 832c10d05f..24dc4d2a1f 100644 --- a/docs/technical/architecture/subsystems/usage/counters.md +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -119,7 +119,7 @@ Full keyspace conventions in `internal/storage/usagestore/keys.go`. The `[ledger ### Why the usagestore counters are not a checker target (design decision, not a gap) -The checker (invariant #8) verifies projections persisted **in the primary Pebble store** — the store it operates on: `Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, idempotency outcomes, the index registry, and the exclusion projection above. The usagestore is a **distinct, peer secondary Pebble instance** (`/usage/`) holding a *derived cache* of counters, rebuildable from the audit chain via the automatic boot/tick fold (and `usagestore.Reset()` on rollback detection). Its authoritativeness is explicitly bounded by "eventually consistent with the FSM". +The checker (invariant #8) verifies projections persisted **in the primary Pebble store** — the store it operates on: `Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, idempotency outcomes, the index registry, and the exclusion projection above. The usagestore is a **distinct, peer secondary Pebble instance** (`/usage/`) holding a *derived cache* of counters, rebuilt from the audit chain by the boot/tick fold, which folds forward from the persisted cursor (from an empty cursor, i.e. `usagestore.Reset()` or a wiped `/usage/`, that replays from audit sequence 0). There is no automatic rollback rewind — the audit chain is append-only, so the cursor never sits ahead of the head. Its authoritativeness is explicitly bounded by "eventually consistent with the FSM". Consequently, the three volume-annotation categories are deliberately **not** given a dedicated usagestore checker pass: diff --git a/internal/bootstrap/module.go b/internal/bootstrap/module.go index 329135dd20..fbaa413c35 100644 --- a/internal/bootstrap/module.go +++ b/internal/bootstrap/module.go @@ -679,9 +679,11 @@ func Module() fx.Option { // store (template usage + per-ledger event counters). Notifications // arrive via the dedicated `name:"usage"` FanOut target. fx.Annotate(func(store *dal.Store, us *usagestore.Store, notifications *signal.Notifications, logger logging.Logger, meterProvider metric.MeterProvider, cfg Config) *usagebuilder.Builder { - // Share the audit indexer's rebuild threshold — both tail the same - // audit chain, so the gap heuristic that catches a rollback-then- - // catch-up race applies identically (auditindexer parity). + // Final arg is batchSize; 0 selects usagebuilder.DefaultBatchSize. + // No rollback/catch-up guard is needed: audit entries are written + // only for committed Raft entries, so the audit chain is + // append-only and the persisted cursor can never sit ahead of the + // head — the builder only ever moves forward. return usagebuilder.NewBuilder(store, us, notifications, logger, meterProvider.Meter("usage.builder"), 0) }, fx.ParamTags(``, ``, `name:"usage"`, ``, ``, ``)), httpcompat.NewServer, diff --git a/internal/storage/usagestore/store.go b/internal/storage/usagestore/store.go index 1a94c91d64..b629e69a38 100644 --- a/internal/storage/usagestore/store.go +++ b/internal/storage/usagestore/store.go @@ -190,17 +190,17 @@ func (s *Store) WriteProgress(batch *dal.WriteSession, sequence uint64) error { // Reset wipes every projection row (all per-template usage records and all // per-ledger counters) and clears the persisted progress cursor, so the next -// boot replays from audit sequence 0. Used when the builder detects that the -// primary store was rolled back beneath the persisted cursor: the retained -// rows reflect audit entries that no longer exist, so a clean in-place rebuild -// on the next boot/tick is how the projection reconverges. +// boot replays from audit sequence 0. It is a full-rebuild primitive: the +// projection reconverges by re-folding the audit chain from the start. The +// builder itself never calls this for a rollback — the audit chain is +// append-only so the persisted cursor can never sit ahead of the head; Reset +// exists for an explicit operator-triggered rebuild of the side-store. // // The two ledger-scoped prefixes (PrefixTemplate 0x01, PrefixCounter 0x02) are // contiguous, so one DeleteRange over [0x01, 0x03) covers both; the internal -// progress key ([0xFE][0x01]) is deleted point-wise. A crash mid-reset is -// safe: the cursor is either still ahead (rollback re-detected next boot) or -// already gone (replay from 0), so the rows can never survive with a stale -// non-zero cursor. +// progress key ([0xFE][0x01]) is deleted point-wise. Rows and cursor are wiped +// in a single Pebble batch, so a crash applies all of it or none of it — the +// rows can never survive with a stale non-zero cursor. func (s *Store) Reset() error { batch := s.NewBatch() From 6f4519fac18bd1e52c681459736c9b5351e7663d Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 16 Jul 2026 15:12:24 +0200 Subject: [PATCH 29/35] fix(EN-1334): peer-store the usage counters after release/v3.0 merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release/v3.0 merge brought in compareBoundaries (checker) + RebuildDelta (backup) code that reads/writes the per-ledger usage counters (PostingCount, RevertCount, NumscriptExecutionCount, VolumeCount, MetadataCount, ReferenceCount) on LedgerBoundaries — but EN-1334 moved those counters to the usagestore peer secondary store, so the fields no longer exist there (compile break). Resolve in the peer-store direction, consistent with the checker-scope framing merged in #1581: the usagestore is a peer secondary store, out of main-store checker scope by construction; its counters are a rebuild-health concern, not an invariant-#8 main-store projection. - checker.go: compareBoundaries now verifies only NextTransactionId/NextLogId; dropped the counter folds in advanceExpectedBoundaries / collectAuditOrderBoundaryEffects (kept the NextTransactionId advances) and the 6 counter rows + attribute recounts; removed the now-unused countLedgerAttributeKeys helper. - rebuild.go: RebuildDelta no longer folds those counters onto LedgerBoundaries (kept NextTransactionId/NextLogId + mirror fill-gap advances); removed the countNetAttributes/countAttributeKeys helpers and the trimmed recordTransactionBoundary signature. - tests: dropped assertions on the removed main-store counters (kept the NextTransactionId/NextLogId + reference-index checks). - AGENTS.md invariant #8: compareBoundaries clause updated — usage counters live in the usagestore peer store, out of main-store checker scope. Verified: go build ./..., go test ./internal/application/check/... ./internal/infra/backup/..., golangci-lint — all green. --- AGENTS.md | 2 +- internal/application/check/checker.go | 98 +++++------------- internal/application/check/checker_test.go | 28 +---- internal/infra/backup/rebuild.go | 113 ++++----------------- internal/infra/backup/rebuild_test.go | 109 +------------------- 5 files changed, 51 insertions(+), 299 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 938385c748..c25c786e2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ This document contains rules and conventions for AI agents working on this codeb **Volume preload is load-bearing for the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` split.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, mis-routes the tuple between the `new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` per-log lists, and inflates `usagestore.CounterVolume`. Because `usagestore` is a rebuildable peer side-store held **outside** checker scope (invariant #8 verifies only primary-Pebble-store projections), no checker pass would surface the drift; it persists until an explicit usage rebuild. That absence of a backstop makes the preload correctness even more load-bearing, not less. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. 7. **Never silently skip a "should not happen" branch** — A branch that is reachable only if an invariant is violated (nil where the contract says non-nil, a state we believe unreachable, a cache miss after a guaranteed preload, etc.) MUST surface a loud signal: `return fmt.Errorf("invariant: ...")` so it bubbles up, or `assert.Unreachable(...)` for SUT-level invariants exercised under antithesis. A silent `return nil` / `continue` on these branches hides real bugs — particularly catastrophic in the FSM apply path, where a no-op desyncs nodes from each other. Branches that represent genuine runtime conditions (cache miss as an expected outcome, stale proposal, deleted entity) keep their soft `return nil`. The distinction is whether the case is *expected* (soft skip OK) or *impossible by design* (must fail loudly). The comment must say *why* the case is impossible so a reader can decide whether to add a hard fail or relax the rule. -8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. Two scope refinements: (a) this applies to the **primary FSM store** — the single store `Check()` opens and walks; **peer secondary stores** (the `readstore` inverted index today; the `usagestore` counters forthcoming with EN-1334) are out of *main-store checker* scope *by construction* since `Check()` never opens them. That scope carve-out is **not** a claim they are integrity-safe: the readstore serves READY indexes directly to business-visible queries with no scan fallback, and its automated detect/drop/rebuild is **not yet wired** (tracked under `EN-1323`), so a corrupted or tampered index is a **current open integrity gap on the peer-store side** — a per-replica rebuild-health concern of the index builder, not an invariant-#8 main-store concern. Out of the main-store checker's mandate, not out of every integrity concern. (b) A primary-store projection may be exempted from a dedicated pass only when it is either (i) deterministically rebuildable from still-retained verified state through a real, *wired* rebuild path — not rebuildable merely in principle — or (ii) purely informational and intentionally carried across restore (cf. `BuildStatus`). These are distinct bases: of the *Known projection gaps* below, `DefaultEnforcementMode` and `SubAttrLedgerMetadata` qualify under (i) via `RebuildDelta`, while `EphemeralEvictedCount` / `TransientUsedCount` qualify under (ii) — they are NOT rebuilt by `RebuildDelta`, so the wired-rebuild basis must not be claimed for them. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus), and `compareMirrorV2LogID` (stored `LedgerBoundaries.last_mirror_v2_log_id` **==** `max(audited MirrorIngest.v2_log_id)` per ledger — a full equality check, `CHECK_STORE_ERROR_TYPE_MIRROR_V2LOGID_MISMATCH` on ANY divergence; the FSM enforces a contiguous applied prefix so at rest the two must be exactly equal, and the baseline floor is seeded from archived `Boundary` rows now included in the compact baseline snapshot; pre-field clusters are unsupported, no backfill leniency), and `compareSchema` (per-ledger `LedgerInfo.MetadataSchema` vs the replayed CreateLedger.initial_schema + SetMetadataFieldType/RemovedMetadataFieldType logs; under archived chapters the boundary schema is seeded from the baseline checkpoint, which carries LedgerInfo for this purpose), and `compareAccountTypes` (per-ledger `LedgerInfo.AccountTypes` vs the replayed AddAccountType/RemoveAccountType logs, baseline-seeded under archiving like the schema), and `compareLedgerPresence` (the live ledger set in the store must match the audit-derived set both ways: every audit-live ledger — CreateLedger with no later DeleteLedger, or a non-deleted baseline ledger under archiving — must have a *live* stored `LedgerInfo` else `MISSING_LEDGER` (covers an entry deleted outright OR tampered to a soft-deleted tombstone, which the compareSchema/compareAccountTypes loops skip), and every live stored `LedgerInfo` must be audit-backed else `UNAUDITED_LEDGER` (an injected empty ledger row carries no schema/types for the projection passes to flag). The audit-derived set is NEVER seeded from the live store — under archiving it comes from the baseline checkpoint, keeping the check independent of the data it verifies), and `compareReferences` (the SubAttrReference reference→txID uniqueness index vs the references replayed from CreatedTransaction/RevertedTransaction logs, baseline-seeded under archiving; verified both ways — missing, unaudited, and retargeted rows are all flagged; rows for deleted or cleanup-pending ledgers are skipped since they legitimately linger until a covering purge runs deleteLedgerData), and `compareBoundaries` (per-ledger `LedgerBoundaries` vs the checker’s re-derivation: NextTransactionId/NextLogId/PostingCount/RevertCount from the replayed logs plus the chain-bound AuditItem order effects — mirror fill-gap advances and NumscriptExecutionCount live on the orders, not the ledger-log stream — baseline-seeded under archiving; VolumeCount/MetadataCount/ReferenceCount are compared against a recount of the stored attribute rows they summarize, which their own passes verify entry-by-entry, so the chain is rows↔audit, counter↔rows; EphemeralEvictedCount/TransientUsedCount are intentionally excluded — informational, carried across restore, cf. BuildStatus); extend the list as new persisted projections land. **Known projection gaps**: `LedgerBoundaries.EphemeralEvictedCount` / `TransientUsedCount` are excluded from `compareBoundaries` and carried across restore rather than re-derived — informational counters (cf. BuildStatus); `LedgerInfo.DefaultEnforcementMode` and the ledger-metadata attribute (`SubAttrLedgerMetadata`) have no compare pass yet (both are rebuilt on restore by RebuildDelta). +8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. Two scope refinements: (a) this applies to the **primary FSM store** — the single store `Check()` opens and walks; **peer secondary stores** (the `readstore` inverted index today; the `usagestore` counters forthcoming with EN-1334) are out of *main-store checker* scope *by construction* since `Check()` never opens them. That scope carve-out is **not** a claim they are integrity-safe: the readstore serves READY indexes directly to business-visible queries with no scan fallback, and its automated detect/drop/rebuild is **not yet wired** (tracked under `EN-1323`), so a corrupted or tampered index is a **current open integrity gap on the peer-store side** — a per-replica rebuild-health concern of the index builder, not an invariant-#8 main-store concern. Out of the main-store checker's mandate, not out of every integrity concern. (b) A primary-store projection may be exempted from a dedicated pass only when it is either (i) deterministically rebuildable from still-retained verified state through a real, *wired* rebuild path — not rebuildable merely in principle — or (ii) purely informational and intentionally carried across restore (cf. `BuildStatus`). These are distinct bases: of the *Known projection gaps* below, `DefaultEnforcementMode` and `SubAttrLedgerMetadata` qualify under (i) via `RebuildDelta`, while `EphemeralEvictedCount` / `TransientUsedCount` qualify under (ii) — they are NOT rebuilt by `RebuildDelta`, so the wired-rebuild basis must not be claimed for them. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus), and `compareMirrorV2LogID` (stored `LedgerBoundaries.last_mirror_v2_log_id` **==** `max(audited MirrorIngest.v2_log_id)` per ledger — a full equality check, `CHECK_STORE_ERROR_TYPE_MIRROR_V2LOGID_MISMATCH` on ANY divergence; the FSM enforces a contiguous applied prefix so at rest the two must be exactly equal, and the baseline floor is seeded from archived `Boundary` rows now included in the compact baseline snapshot; pre-field clusters are unsupported, no backfill leniency), and `compareSchema` (per-ledger `LedgerInfo.MetadataSchema` vs the replayed CreateLedger.initial_schema + SetMetadataFieldType/RemovedMetadataFieldType logs; under archived chapters the boundary schema is seeded from the baseline checkpoint, which carries LedgerInfo for this purpose), and `compareAccountTypes` (per-ledger `LedgerInfo.AccountTypes` vs the replayed AddAccountType/RemoveAccountType logs, baseline-seeded under archiving like the schema), and `compareLedgerPresence` (the live ledger set in the store must match the audit-derived set both ways: every audit-live ledger — CreateLedger with no later DeleteLedger, or a non-deleted baseline ledger under archiving — must have a *live* stored `LedgerInfo` else `MISSING_LEDGER` (covers an entry deleted outright OR tampered to a soft-deleted tombstone, which the compareSchema/compareAccountTypes loops skip), and every live stored `LedgerInfo` must be audit-backed else `UNAUDITED_LEDGER` (an injected empty ledger row carries no schema/types for the projection passes to flag). The audit-derived set is NEVER seeded from the live store — under archiving it comes from the baseline checkpoint, keeping the check independent of the data it verifies), and `compareReferences` (the SubAttrReference reference→txID uniqueness index vs the references replayed from CreatedTransaction/RevertedTransaction logs, baseline-seeded under archiving; verified both ways — missing, unaudited, and retargeted rows are all flagged; rows for deleted or cleanup-pending ledgers are skipped since they legitimately linger until a covering purge runs deleteLedgerData), and `compareBoundaries` (per-ledger `LedgerBoundaries` vs the checker’s re-derivation: only NextTransactionId/NextLogId are verified here — derived from the replayed logs plus the chain-bound AuditItem order effects (mirror fill-gap advances live on the orders, not the ledger-log stream), baseline-seeded under archiving; the mirror high-water `last_mirror_v2_log_id` is verified separately by `compareMirrorV2LogID`. The per-ledger usage counters — `PostingCount`, `RevertCount`, `NumscriptExecutionCount`, `VolumeCount`, `MetadataCount`, `ReferenceCount` — no longer live on `LedgerBoundaries`: they moved to the `usagestore` peer secondary store (EN-1334) and are out of main-store checker scope **by construction** (per the peer-store carve-out in scope refinement (a) above), their integrity being a peer-store rebuild-health concern rather than an invariant-#8 main-store concern; `EphemeralEvictedCount`/`TransientUsedCount` remain on `LedgerBoundaries` but are intentionally excluded — informational, carried across restore, cf. BuildStatus); extend the list as new persisted projections land. **Known projection gaps**: `LedgerBoundaries.EphemeralEvictedCount` / `TransientUsedCount` are excluded from `compareBoundaries` and carried across restore rather than re-derived — informational counters (cf. BuildStatus); `LedgerInfo.DefaultEnforcementMode` and the ledger-metadata attribute (`SubAttrLedgerMetadata`) have no compare pass yet (both are rebuilt on restore by RebuildDelta). 9. **Never bypass the FSM coverage gate** — Every cache-attribute read on the FSM hot path MUST go through `Scope.GetX(...)` so the per-order `coverage_bits` admit it. Reading the underlying `Registry.X.KeyStore().M` (or any other parent-cache iterator) directly skips the gate and produces non-deterministic FSM behavior: the gate is what binds the order to the admission-declared preload set, and a direct read silently sees keys the proposer never declared. There is NO documented exception — paths that need to iterate (e.g. cascade-on-delete) MUST either declare the relevant `preload.Needs` upfront, defer the work to a lifecycle path (`batch.deleteLedgerData` + `MarkLedgerForCleanup`), or be rejected at design review. New helpers that scan the parent KeyStore from inside an order/TU handler are the violation, even when wrapped in a method on `WriteSet`. The coverage gate exists precisely so admission's declared key set is the FSM's only legitimate read horizon — under no circumstances should the apply path widen it on the fly. diff --git a/internal/application/check/checker.go b/internal/application/check/checker.go index 13ceebabed..baddf92b87 100644 --- a/internal/application/check/checker.go +++ b/internal/application/check/checker.go @@ -5129,9 +5129,11 @@ func trackTxID(m map[string]*bitset.Bitset, ledgerName string, txID uint64) { // advanceExpectedBoundaries mirrors the live apply's boundary advancement for // one replayed ledger log: NextLogId past the log id, and — for transaction -// logs — NextTransactionId past the created id plus the posting / revert -// counters. Mirror fill-gap advances and numscript counts ride on the order, -// not the log; collectAuditOrderBoundaryEffects folds those in. +// logs — NextTransactionId past the created id. Mirror fill-gap advances ride +// on the order, not the log; collectAuditOrderBoundaryEffects folds those in. +// The per-ledger usage counters (posting / revert / numscript / volume / +// metadata / reference) live in the usagestore peer secondary store and are +// out of main-store checker scope, so they are not advanced here. func advanceExpectedBoundaries(expected map[string]*raftcmdpb.LedgerBoundaries, ledger string, log *commonpb.LedgerLog) { b, ok := expected[ledger] if !ok { @@ -5143,11 +5145,7 @@ func advanceExpectedBoundaries(expected map[string]*raftcmdpb.LedgerBoundaries, b.NextLogId = next } - var ( - txID uint64 - postings int - isRevert bool - ) + var txID uint64 switch d := log.GetData().GetPayload().(type) { case *commonpb.LedgerLogPayload_CreatedTransaction: @@ -5156,14 +5154,14 @@ func advanceExpectedBoundaries(expected map[string]*raftcmdpb.LedgerBoundaries, return } - txID, postings = tx.GetId(), len(tx.GetPostings()) + txID = tx.GetId() case *commonpb.LedgerLogPayload_RevertedTransaction: revertTx := d.RevertedTransaction.GetRevertTransaction() if revertTx == nil { return } - txID, postings, isRevert = revertTx.GetId(), len(revertTx.GetPostings()), true + txID = revertTx.GetId() default: return } @@ -5171,21 +5169,15 @@ func advanceExpectedBoundaries(expected map[string]*raftcmdpb.LedgerBoundaries, if next := txID + 1; next > b.GetNextTransactionId() { b.NextTransactionId = next } - - b.PostingCount += uint64(postings) - - if isRevert { - b.RevertCount++ - } } // collectAuditOrderBoundaryEffects iterates the post-archive AuditItem rows -// and folds the order-level boundary effects (mirror fill-gap advances, -// numscript executions) into the expected boundaries — the same fold -// backup.RebuildDelta performs on restore. Items with log_sequence 0 (failed -// proposals, idempotent replays) or at/below the archive boundary contribute -// nothing; effects for ledgers without an expectation (deleted, or flagged -// UNKNOWN_LEDGER during replay) are skipped. +// and folds the order-level boundary effects (mirror fill-gap advances) into +// the expected boundaries — the same fold backup.RebuildDelta performs on +// restore. Items with log_sequence 0 (failed proposals, idempotent replays) or +// at/below the archive boundary contribute nothing; effects for ledgers +// without an expectation (deleted, or flagged UNKNOWN_LEDGER during replay) +// are skipped. func (c *Checker) collectAuditOrderBoundaryEffects(reader dal.PebbleReader, archiveEndSeq uint64, expected map[string]*raftcmdpb.LedgerBoundaries) error { iter, err := reader.NewIter(&pebble.IterOptions{ LowerBound: []byte{dal.ZoneCold, dal.SubColdAuditItem}, @@ -5231,44 +5223,21 @@ func (c *Checker) collectAuditOrderBoundaryEffects(reader dal.PebbleReader, arch b.NextTransactionId = next } } - - if effects.IsNumscript { - b.NumscriptExecutionCount++ - } } return iter.Error() } -// countLedgerAttributeKeys counts the stored keys under a ledger's canonical -// prefix in the attribute zone. -func countLedgerAttributeKeys[V proto.Message](attr *attributes.Attribute[V], reader dal.PebbleReader, ledger string) (uint64, error) { - si, err := attr.NewStreamingIter(reader, domain.LedgerScopedPrefix(ledger)) - if err != nil { - return 0, err - } - - defer func() { _ = si.Close() }() - - var count uint64 - for si.Next() { - count++ - } - - return count, si.Err() -} - // compareBoundaries verifies each ledger's stored LedgerBoundaries against the -// checker's re-derivation. The id fields and replay-derivable counters -// (NextTransactionId, NextLogId, PostingCount, RevertCount, -// NumscriptExecutionCount) come from the replayed logs plus the chain-bound -// audit-order effects, baseline-seeded under archiving. The net key counters -// (VolumeCount, MetadataCount, ReferenceCount) are compared against a recount -// of the stored attribute rows they summarize — the rows themselves are -// verified entry-by-entry by compareVolumes / compareMetadata / -// compareReferences, so the verification chain is rows↔audit, counter↔rows. -// EphemeralEvictedCount / TransientUsedCount are informational and excluded -// (cf. compareIndexes' BuildStatus exclusion). +// checker's re-derivation. Only the id fields (NextTransactionId, NextLogId) +// are verified here — they come from the replayed logs plus the chain-bound +// audit-order effects, baseline-seeded under archiving; the mirror high-water +// (last_mirror_v2_log_id) is verified separately by compareMirrorV2LogID. The +// per-ledger usage counters (PostingCount, RevertCount, +// NumscriptExecutionCount, VolumeCount, MetadataCount, ReferenceCount) no +// longer live on LedgerBoundaries — they moved to the usagestore peer +// secondary store and are out of main-store checker scope by construction +// (their integrity is a peer-store rebuild-health concern, not invariant #8). func (c *Checker) compareBoundaries(ctx context.Context, reader dal.PebbleReader, expected map[string]*raftcmdpb.LedgerBoundaries, callback func(*servicepb.CheckStoreEvent)) error { stored := make(map[string]*raftcmdpb.LedgerBoundaries) @@ -5313,21 +5282,6 @@ func (c *Checker) compareBoundaries(ctx context.Context, reader dal.PebbleReader continue } - volumeRows, err := countLedgerAttributeKeys(c.attrs.Volume, reader, name) - if err != nil { - return fmt.Errorf("counting stored volumes for ledger %q: %w", name, err) - } - - metadataRows, err := countLedgerAttributeKeys(c.attrs.Metadata, reader, name) - if err != nil { - return fmt.Errorf("counting stored metadata for ledger %q: %w", name, err) - } - - referenceRows, err := countLedgerAttributeKeys(c.attrs.References, reader, name) - if err != nil { - return fmt.Errorf("counting stored references for ledger %q: %w", name, err) - } - fields := []struct { field string expected uint64 @@ -5335,12 +5289,6 @@ func (c *Checker) compareBoundaries(ctx context.Context, reader dal.PebbleReader }{ {"nextTransactionId", exp.GetNextTransactionId(), st.GetNextTransactionId()}, {"nextLogId", exp.GetNextLogId(), st.GetNextLogId()}, - {"postingCount", exp.GetPostingCount(), st.GetPostingCount()}, - {"revertCount", exp.GetRevertCount(), st.GetRevertCount()}, - {"numscriptExecutionCount", exp.GetNumscriptExecutionCount(), st.GetNumscriptExecutionCount()}, - {"volumeCount", volumeRows, st.GetVolumeCount()}, - {"metadataCount", metadataRows, st.GetMetadataCount()}, - {"referenceCount", referenceRows, st.GetReferenceCount()}, } for _, f := range fields { diff --git a/internal/application/check/checker_test.go b/internal/application/check/checker_test.go index 9bece40f01..fdcfbe0f00 100644 --- a/internal/application/check/checker_test.go +++ b/internal/application/check/checker_test.go @@ -197,31 +197,11 @@ func (e *testEngine) processAndCommit(orders ...*raftcmdpb.Order) []*commonpb.Lo require.NoError(e.t, err) } - // Write ledger boundaries with the net key counters the real WriteSet - // maintains via updateBoundaryCounters — each counter equals the number - // of stored rows under the ledger's canonical prefix. + // Write ledger boundaries. The main-store boundary row carries only the id + // fields (NextTransactionId / NextLogId) and the mirror high-water; the + // per-ledger usage counters moved to the usagestore peer secondary store + // and are out of main-store checker scope. for name, b := range e.boundaries { - prefix := string(domain.LedgerScopedPrefix(name)) - b.VolumeCount, b.MetadataCount, b.ReferenceCount = 0, 0, 0 - - for key, vp := range e.volumes { - if strings.HasPrefix(key, prefix) && (vp.GetInput() != nil || vp.GetOutput() != nil) { - b.VolumeCount++ - } - } - - for key := range e.metadata { - if strings.HasPrefix(key, prefix) { - b.MetadataCount++ - } - } - - for key := range e.references { - if strings.HasPrefix(key, prefix) { - b.ReferenceCount++ - } - } - _, err := e.attrs.Boundary.Set(batch, domain.LedgerKey{Name: name}.Bytes(), b) require.NoError(e.t, err) } diff --git a/internal/infra/backup/rebuild.go b/internal/infra/backup/rebuild.go index 373c28960d..09011ad3ff 100644 --- a/internal/infra/backup/rebuild.go +++ b/internal/infra/backup/rebuild.go @@ -10,7 +10,6 @@ import ( "github.com/cockroachdb/pebble/v2" "github.com/holiman/uint256" - "google.golang.org/protobuf/proto" logging "github.com/formancehq/go-libs/v5/pkg/observe/log" @@ -408,19 +407,10 @@ func RebuildDelta( return fmt.Errorf("applying audit order effects to boundaries: %w", err) } - // Net attribute counts (VolumeCount, MetadataCount, ReferenceCount) are - // read from the committed 0xF1 state, so this runs after the attribute - // commit. The boundaries themselves are then written in their own batch. - countHandle, err := store.NewDirectReadHandle() - if err != nil { - return fmt.Errorf("opening read handle for boundary counts: %w", err) - } - defer func() { _ = countHandle.Close() }() - - if err := writer.countNetAttributes(countHandle); err != nil { - return fmt.Errorf("counting net attributes for boundaries: %w", err) - } - + // The rebuilt boundaries (NextTransactionId / NextLogId / mirror + // high-water) are written in their own batch after the attribute commit. + // The per-ledger usage counters live in the usagestore peer secondary + // store and are rebuilt there, so they are not derived or written here. writer.batch = store.OpenWriteSession() if err := writer.flushBoundaries(); err != nil { @@ -689,24 +679,23 @@ type attributeReplayWriter struct { // LedgerBoundaries per touched ledger. The apply path preloads boundaries // from the SubAttrBoundary attribute, which the log replay does not write - // per-entry: NextTransactionId / NextLogId and the per-transaction counters - // accumulate here, seeded from the checkpoint on first touch via - // readHandle, and are flushed in their own batch after the attribute - // commit (net counts are derived from the committed 0xF1 state). + // per-entry: NextTransactionId / NextLogId accumulate here, seeded from the + // checkpoint on first touch via readHandle, and are flushed in their own + // batch after the attribute commit. The per-ledger usage counters live in + // the usagestore peer secondary store, not here. boundaries map[string]*raftcmdpb.LedgerBoundaries readHandle dal.PebbleReader } // applyAuditOrderEffects folds order-level boundary effects that the ledger-log // stream does not carry: MirrorFillGap's skipped transaction ids (FilledGapLog -// keeps only the original v2 id) and NumscriptExecutionCount (CreatedTransaction -// logs record the resulting postings, not the content source). Both live on the -// order itself, which AuditItem.serialized_order preserves — bound into the -// audit hash chain and shipped by the incremental export's auditItem segments. +// keeps only the original v2 id). These live on the order itself, which +// AuditItem.serialized_order preserves — bound into the audit hash chain and +// shipped by the incremental export's auditItem segments. // // Items with log_sequence == 0 (failed proposals, idempotent replays) and items // at or below fromLogSeq (already folded into the checkpoint) contribute -// nothing. See replay.OrderEffects for why script detection is exact. +// nothing. func (w *attributeReplayWriter) applyAuditOrderEffects(reader dal.PebbleReader, fromLogSeq, fromAuditSeq uint64) error { lower := dal.NewKeyBuilder(). PutZonePrefix(dal.ZoneCold, dal.SubColdAuditItem). @@ -767,10 +756,6 @@ func (w *attributeReplayWriter) applyAuditOrderEffects(reader dal.PebbleReader, b.NextTransactionId = next } } - - if effects.IsNumscript { - b.NumscriptExecutionCount++ - } } return iter.Error() @@ -874,11 +859,12 @@ func (w *attributeReplayWriter) advanceLogID(ledgerName string, logID uint64) er return nil } -// recordTransactionBoundary advances a ledger's boundaries for one created -// transaction: NextTransactionId past its id, PostingCount by its postings, and -// RevertCount when it reverts another (revert reversals carry revertsTransaction -// and flow through CreateTransaction like any other tx). -func (w *attributeReplayWriter) recordTransactionBoundary(canonicalKey []byte, postingCount int, revertsTransaction uint64) error { +// recordTransactionBoundary advances a ledger's NextTransactionId past a created +// transaction's id (revert reversals carry revertsTransaction and flow through +// CreateTransaction like any other tx). The per-ledger usage counters +// (PostingCount, RevertCount) live in the usagestore peer secondary store and +// are rebuilt there, so they are not advanced here. +func (w *attributeReplayWriter) recordTransactionBoundary(canonicalKey []byte) error { var tk domain.TransactionKey if err := tk.Unmarshal(canonicalKey); err != nil { return fmt.Errorf("parsing transaction key for boundaries: %w", err) @@ -893,70 +879,9 @@ func (w *attributeReplayWriter) recordTransactionBoundary(canonicalKey []byte, p b.NextTransactionId = next } - b.PostingCount += uint64(postingCount) - - if revertsTransaction != 0 { - b.RevertCount++ - } - return nil } -// countNetAttributes sets VolumeCount, MetadataCount and ReferenceCount for -// every touched ledger to the number of persisted keys in the committed 0xF1 -// state. These are net (last-value) counts, so counting the final keys is -// exact: ephemeral and transient volumes have already been purged, matching the -// live counters. -// -// NextTransactionId, NextLogId, PostingCount, RevertCount accumulate during -// the log replay; NumscriptExecutionCount and the mirror fill-gap advances come -// from applyAuditOrderEffects. EphemeralEvictedCount / TransientUsedCount are -// carried from the checkpoint unchanged. -func (w *attributeReplayWriter) countNetAttributes(reader dal.PebbleReader) error { - for name, b := range w.boundaries { - prefix := domain.LedgerScopedPrefix(name) - - volumeCount, err := countAttributeKeys(w.volume, reader, prefix) - if err != nil { - return fmt.Errorf("counting volumes for ledger %q: %w", name, err) - } - - metadataCount, err := countAttributeKeys(w.metadata, reader, prefix) - if err != nil { - return fmt.Errorf("counting metadata for ledger %q: %w", name, err) - } - - referenceCount, err := countAttributeKeys(w.references, reader, prefix) - if err != nil { - return fmt.Errorf("counting references for ledger %q: %w", name, err) - } - - b.VolumeCount = volumeCount - b.MetadataCount = metadataCount - b.ReferenceCount = referenceCount - } - - return nil -} - -// countAttributeKeys counts the persisted keys under a canonical prefix (the -// fixed-width ledger name) in the 0xF1 attribute zone. -func countAttributeKeys[V proto.Message](attr *attributes.Attribute[V], reader dal.PebbleReader, prefix []byte) (uint64, error) { - si, err := attr.NewStreamingIter(reader, prefix) - if err != nil { - return 0, err - } - - defer func() { _ = si.Close() }() - - var count uint64 - for si.Next() { - count++ - } - - return count, si.Err() -} - // flushBoundaries writes every touched ledger's boundaries to the SubAttrBoundary // attribute. Called after countNetAttributes, in its own post-commit batch. func (w *attributeReplayWriter) flushBoundaries() error { @@ -1084,7 +1009,7 @@ func (w *attributeReplayWriter) getTx(canonicalKey []byte) (*commonpb.Transactio } func (w *attributeReplayWriter) CreateTransaction(canonicalKey []byte, seq uint64, timestamp *commonpb.Timestamp, metadata map[string]*commonpb.MetadataValue, postings []*commonpb.Posting, revertsTransaction uint64) error { - if err := w.recordTransactionBoundary(canonicalKey, len(postings), revertsTransaction); err != nil { + if err := w.recordTransactionBoundary(canonicalKey); err != nil { return err } diff --git a/internal/infra/backup/rebuild_test.go b/internal/infra/backup/rebuild_test.go index c3bc0da5c5..0dc9a5e3c3 100644 --- a/internal/infra/backup/rebuild_test.go +++ b/internal/infra/backup/rebuild_test.go @@ -145,21 +145,6 @@ func fillGapOrder(ledger string, v2LogID uint64, skippedIDs ...uint64) *raftcmdp } } -func createTransactionOrder(ledger string, ct *raftcmdpb.CreateTransactionOrder) *raftcmdpb.Order { - return &raftcmdpb.Order{ - Type: &raftcmdpb.Order_LedgerScoped{ - LedgerScoped: &raftcmdpb.LedgerScopedOrder{ - Ledger: ledger, - Payload: &raftcmdpb.LedgerScopedOrder_Apply{ - Apply: &raftcmdpb.LedgerApplyOrder{ - Data: &raftcmdpb.LedgerApplyOrder_CreateTransaction{CreateTransaction: ct}, - }, - }, - }, - }, - } -} - func auditSuccess(seq, minLogSeq, maxLogSeq uint64) *auditpb.AuditEntry { return &auditpb.AuditEntry{ Sequence: seq, @@ -265,18 +250,15 @@ func TestRebuildDelta_ReplaysEphemeralPurgeAtProposalBoundary(t *testing.T) { require.Equal(t, "8", pair.GetInput().ToBigInt().String()) require.Equal(t, "5", pair.GetOutput().ToBigInt().String()) - // Boundaries are rebuilt into the attribute zone with reconstructed - // counters. Log ids here equal the log sequence (test fixture), so - // NextLogId is max(seq)+1 over the four apply logs (seqs 2-5). + // Boundaries are rebuilt into the attribute zone with the id fields. Log + // ids here equal the log sequence (test fixture), so NextLogId is + // max(seq)+1 over the four apply logs (seqs 2-5). The per-ledger usage + // counters live in the usagestore peer secondary store, not here. boundary, err := attrs.Boundary.Get(handle, domain.LedgerKey{Name: "ledger"}.Bytes()) require.NoError(t, err) require.NotNil(t, boundary, "boundaries must be reconstructed into the attribute zone") require.Equal(t, uint64(4), boundary.GetNextTransactionId(), "3 transactions created") require.Equal(t, uint64(6), boundary.GetNextLogId()) - require.Equal(t, uint64(3), boundary.GetPostingCount(), "one posting per transaction") - require.Equal(t, uint64(0), boundary.GetRevertCount()) - require.Equal(t, uint64(2), boundary.GetVolumeCount(), "orders:1 + world; ephemeral orders:1 kept because input != output") - require.Equal(t, uint64(0), boundary.GetMetadataCount()) } func TestRebuildDelta_ReconstructsTransactionReference(t *testing.T) { @@ -304,10 +286,6 @@ func TestRebuildDelta_ReconstructsTransactionReference(t *testing.T) { require.NoError(t, err) require.NotNil(t, ref, "reference index must be reconstructed") require.Equal(t, uint64(1), ref.GetTransactionId()) - - boundary, err := attrs.Boundary.Get(handle, domain.LedgerKey{Name: "ledger"}.Bytes()) - require.NoError(t, err) - require.Equal(t, uint64(1), boundary.GetReferenceCount()) } func TestRebuildDelta_AdvancesBoundariesForMirrorFillGap(t *testing.T) { @@ -339,51 +317,6 @@ func TestRebuildDelta_AdvancesBoundariesForMirrorFillGap(t *testing.T) { require.Equal(t, uint64(3), boundary.GetNextLogId()) } -func TestRebuildDelta_CountsNumscriptExecutionsFromAuditOrders(t *testing.T) { - t.Parallel() - - store := newRebuildTestStore(t) - - batch := store.OpenWriteSession() - require.NoError(t, batch.SetProto(coldLogKey(1), createLedgerLog(1, "ledger", 1))) - for seq := uint64(2); seq <= 4; seq++ { - require.NoError(t, batch.SetProto(coldLogKey(seq), applyLedgerLog(seq, "ledger", - createdTransactionPayload(seq-1, rebuildTestPosting("world", "alice", "USD", 10)), - ))) - } - - // Inline script and library reference both count; a postings order and a - // failed script order (log_sequence 0) do not. - scriptOrder := createTransactionOrder("ledger", &raftcmdpb.CreateTransactionOrder{ - Script: &commonpb.Script{Plain: "send [USD 10] (source = @world destination = @alice)"}, - }) - refOrder := createTransactionOrder("ledger", &raftcmdpb.CreateTransactionOrder{ - NumscriptReference: &raftcmdpb.NumscriptReference{Name: "payout", Version: "1"}, - }) - postingsOrder := createTransactionOrder("ledger", &raftcmdpb.CreateTransactionOrder{ - Postings: []*commonpb.Posting{rebuildTestPosting("world", "alice", "USD", 10)}, - }) - require.NoError(t, batch.SetProto(coldAuditItemKey(1, 0), auditItem(t, 2, scriptOrder))) - require.NoError(t, batch.SetProto(coldAuditItemKey(2, 0), auditItem(t, 3, refOrder))) - require.NoError(t, batch.SetProto(coldAuditItemKey(3, 0), auditItem(t, 4, postingsOrder))) - require.NoError(t, batch.SetProto(coldAuditItemKey(4, 0), auditItem(t, 0, scriptOrder))) - require.NoError(t, batch.Commit()) - - require.NoError(t, RebuildDelta(context.Background(), testLogger(), store, 0, 0)) - - handle, err := store.NewDirectReadHandle() - require.NoError(t, err) - defer func() { _ = handle.Close() }() - - attrs := attributes.New() - - boundary, err := attrs.Boundary.Get(handle, domain.LedgerKey{Name: "ledger"}.Bytes()) - require.NoError(t, err) - require.NotNil(t, boundary) - require.Equal(t, uint64(2), boundary.GetNumscriptExecutionCount(), - "inline script + library reference count; postings and failed orders do not") -} - func TestRebuildDelta_ReplaysLedgerMetadata(t *testing.T) { t.Parallel() @@ -894,37 +827,3 @@ func TestAttributeReplayWriter_RemoveFieldTypeNoSchemaIsNoOp(t *testing.T) { require.NoError(t, writer.RemoveMetadataFieldType("ledger", commonpb.TargetType_TARGET_TYPE_ACCOUNT, "k")) } - -// TestRebuildDelta_CountsDoNotBleedAcrossPrefixLedgers: net counts must scan -// with the padded ledger prefix — an unpadded prefix also matches every -// ledger whose name extends it. -func TestRebuildDelta_CountsDoNotBleedAcrossPrefixLedgers(t *testing.T) { - t.Parallel() - - store := newRebuildTestStore(t) - - batch := store.OpenWriteSession() - require.NoError(t, batch.SetProto(coldLogKey(1), createLedgerLog(1, "pay", 1))) - require.NoError(t, batch.SetProto(coldLogKey(2), createLedgerLog(2, "payments", 2))) - require.NoError(t, batch.SetProto(coldLogKey(3), applyLedgerLog(3, "pay", - createdTransactionPayload(1, rebuildTestPosting("world", "alice", "USD", 10)), - ))) - require.NoError(t, batch.SetProto(coldLogKey(4), applyLedgerLog(4, "payments", - createdTransactionPayload(1, rebuildTestPosting("world", "bob", "USD", 10)), - ))) - require.NoError(t, batch.Commit()) - - require.NoError(t, RebuildDelta(context.Background(), testLogger(), store, 0, 0)) - - handle, err := store.NewDirectReadHandle() - require.NoError(t, err) - defer func() { _ = handle.Close() }() - - attrs := attributes.New() - - boundary, err := attrs.Boundary.Get(handle, domain.LedgerKey{Name: "pay"}.Bytes()) - require.NoError(t, err) - require.NotNil(t, boundary) - require.Equal(t, uint64(2), boundary.GetVolumeCount(), - `"pay" must count only its own rows (world + alice), not "payments" rows too`) -} From 0510143f8f288d80ea2b8fbef6257f6f97439f16 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Thu, 16 Jul 2026 16:50:30 +0200 Subject: [PATCH 30/35] fix(EN-1334): skipped orders must not inflate usage counters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flemzord review: dispatch* incremented success-scoped usage counters from the raw order even when the produced log was an OrderSkipped (order committed nothing). - readLog now reports the produced log kind (isCreatedTx / isRevertedTx). - dispatchCreateTransaction: gate the reference / numscript-execution / template-usage increments on a produced CreatedTransaction (skips contribute nothing; posting/volume counters already derive from the produced log). - dispatchRevertTransaction: gate CounterRevert on a produced RevertedTransaction (previously bumped unconditionally before reading the log). - docs/usage/counters.md: intro aligned to the peer-store framing (usagestore is out of main-store checker scope; integrity is rebuild-health, not verified by the checker); Revert/Numscript/Reference deltas documented as per-successful-order. go build ./..., usagebuilder tests, golangci-lint — green. --- .../architecture/subsystems/usage/counters.md | 10 ++++--- .../application/usagebuilder/process_audit.go | 28 +++++++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/technical/architecture/subsystems/usage/counters.md b/docs/technical/architecture/subsystems/usage/counters.md index 24dc4d2a1f..e0b5c2d6e4 100644 --- a/docs/technical/architecture/subsystems/usage/counters.md +++ b/docs/technical/architecture/subsystems/usage/counters.md @@ -1,6 +1,8 @@ # Counters and Storage Schema -This page documents the projections the usagebuilder materialises: what each counter counts, how the FSM plumbs the source data through the log payload, how the counters are keyed in the usagestore, and how the checker verifies them. +This page documents the projections the usagebuilder materialises: what each counter counts, how the FSM plumbs the source data through the log payload, and how the counters are keyed in the usagestore. + +These counters live in the **usagestore**, a peer secondary store. Per the checker-scope framing (CLAUDE.md invariant #8), a peer secondary store is out of the **main-store checker's** scope *by construction* — `Check()` never opens the usagestore — so the checker does **not** verify these counters. Their integrity is a per-replica rebuild-health concern (rebuildable from the audit on demand), not a main-store invariant-#8 projection. For the pipeline that populates these keys, see [usagebuilder.md](usagebuilder.md). @@ -11,9 +13,9 @@ Every counter is keyed by `(ledger, counter_id)` in the usagestore (`internal/st | ID | Name | Source | Delta per event | |----|------|--------|-----------------| | `0x01` | `CounterPosting` | `Transaction.Postings` (`CreatedTransaction`) + `RevertTransaction.Postings` (`RevertedTransaction`) | `+len(Postings)` per applicable log | -| `0x02` | `CounterRevert` | `RevertTransactionOrder` unmarshalled from `AuditItem.SerializedOrder` (covers both direct and mirror-ingested reverts) | `+1` per order | -| `0x03` | `CounterNumscriptExecution` | `CreateTransactionOrder` with a non-empty `Script.Plain` or a non-nil `NumscriptReference` | `+1` per order | -| `0x04` | `CounterReference` | `CreateTransactionOrder.Reference != ""` | `+1` per order | +| `0x02` | `CounterRevert` | `RevertTransactionOrder` unmarshalled from `AuditItem.SerializedOrder` (covers both direct and mirror-ingested reverts) | `+1` per **successful** revert (gated on the produced log being a `RevertedTransaction`; a skipped order — `OrderSkipped` log — counts nothing) | +| `0x03` | `CounterNumscriptExecution` | `CreateTransactionOrder` with a non-empty `Script.Plain` or a non-nil `NumscriptReference` | `+1` per **successful** order (gated on a produced `CreatedTransaction`; skips count nothing) | +| `0x04` | `CounterReference` | `CreateTransactionOrder.Reference != ""` | `+1` per **successful** order (gated on a produced `CreatedTransaction`; skips count nothing) | | `0x05` | `CounterEphemeralEvicted` | `len(LedgerLog.EphemeralVolumes)` per log — the pure-ephemeral tuples (new + purged same log) | `+len(EphemeralVolumes)` | | `0x06` | `CounterTransientUsed` | `len(AppliedProposal.TransientVolumes[ledger].Volumes)` — batch-level, keyed by audit sequence | `+len(TransientVolumes[ledger])` per proposal | | `0x07` | `CounterVolume` | `len(LedgerLog.NewKeptVolumes) − len(LedgerLog.PurgedVolumes)` per log | net change in live volume-key cardinality | diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 903f58a74a..210764be87 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -466,6 +466,16 @@ func (b *Builder) dispatchCreateTransaction( applyVolumeAnnotations(ledger, ann, state, entry) + // A skipped CreateTransaction produces an OrderSkipped log, not a + // CreatedTransaction, so readLog reports no real transaction. Such an order + // committed nothing: it must contribute no reference / numscript-execution / + // template-usage counters (all derived from the raw order below). Posting and + // volume counters above come from the produced log and are already naturally + // zero for a skip. + if !ann.isCreatedTx { + return nil + } + if order.GetReference() != "" { state.addCounter(ledger, usagestore.CounterReference, 1) } @@ -505,13 +515,19 @@ func (b *Builder) dispatchRevertTransaction( state *batchState, entry *entryVolumeState, ) error { - state.addCounter(ledger, usagestore.CounterRevert, 1) - ann, err := b.readLog(ctx, handle, logSeq) if err != nil { return err } + // A skipped RevertTransaction produces an OrderSkipped log; only a produced + // RevertedTransaction is a successful revert. Gate the revert counter on it so + // a skipped revert does not inflate the count. (Posting/volume counters below + // come from the produced log and are already zero for a skip.) + if ann.isRevertedTx { + state.addCounter(ledger, usagestore.CounterRevert, 1) + } + if ann.postings > 0 { state.addCounter(ledger, usagestore.CounterPosting, counterDelta(ann.postings)) } @@ -534,6 +550,12 @@ type logVolumeAnnotations struct { newKept []*commonpb.TouchedVolume // new + kept ephemeral []*commonpb.TouchedVolume // new + purged (pure ephemeral) txTimestamp *commonpb.Timestamp // Transaction.Timestamp on Created/Reverted logs + + // Kind of the produced log. Both false when the order was skipped + // (OrderSkipped) or produced no transaction — the order committed nothing, + // so it must not contribute success-scoped usage counters. + isCreatedTx bool + isRevertedTx bool } // readLog fetches the log at logSeq and returns its posting count plus the @@ -580,10 +602,12 @@ func (b *Builder) readLog(ctx context.Context, handle dal.PebbleGetter, logSeq u tx := p.CreatedTransaction.GetTransaction() result.postings = len(tx.GetPostings()) result.txTimestamp = tx.GetTimestamp() + result.isCreatedTx = true case *commonpb.LedgerLogPayload_RevertedTransaction: tx := p.RevertedTransaction.GetRevertTransaction() result.postings = len(tx.GetPostings()) result.txTimestamp = tx.GetTimestamp() + result.isRevertedTx = true } return result, nil From aae1776826d6f7dc1f746d2a639fa43aa5988428 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 17 Jul 2026 09:55:53 +0200 Subject: [PATCH 31/35] fix(EN-1334): carry color in indexbuilder exclusion + usagebuilder dedup keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The color-of-money merge made TouchedVolume color-aware, but two consumers still keyed volume sets on (account, asset) only: - indexbuilder extractPurgedVolumes built the purged/ephemeral exclusion set without color, while isExcluded looks it up with the posting's color — colored evictions were never matched (stale index rows) and uncolored postings sharing the (account, asset) were wrongly excluded. Now mirrors domain.TouchedVolumeSet. - usagebuilder volumeSetKey omitted color, so two color buckets of one (account, asset) in a single audit entry deduped to one, undercounting VolumeCount / EphemeralEvictedCount. Both now carry color, matching the (account, asset, color) volume identity. --- .../indexbuilder/applied_proposal_sync.go | 14 +++++++++----- .../application/usagebuilder/process_audit.go | 17 +++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/application/indexbuilder/applied_proposal_sync.go b/internal/application/indexbuilder/applied_proposal_sync.go index 81a71736e6..7a47282398 100644 --- a/internal/application/indexbuilder/applied_proposal_sync.go +++ b/internal/application/indexbuilder/applied_proposal_sync.go @@ -238,9 +238,13 @@ func (s *appliedProposalSync) close() error { return nil } -// extractPurgedVolumes returns the set of (account, asset) volumes evicted -// from Pebble at commit by THIS log — the UNION of draining (PurgedVolumes) -// and pure ephemeral (EphemeralVolumes). Both categories share the same +// extractPurgedVolumes returns the set of (account, asset, color) volumes +// evicted from Pebble at commit by THIS log — the UNION of draining +// (PurgedVolumes) and pure ephemeral (EphemeralVolumes). The color dimension is +// carried so the key matches the (account, asset, color) tuple isExcluded looks +// up from each posting — dropping it would leave stale index rows for colored +// evictions and wrongly exclude uncolored postings sharing the (account, asset). +// Both categories share the same // downstream treatment: their acct->tx mappings must be skipped because // the volume entries no longer exist in the attribute store. // @@ -255,10 +259,10 @@ func extractPurgedVolumes(ledgerLog ledgerLogWithPurgedVolumes) map[domain.Accou out := make(map[domain.AccountAssetKey]struct{}, len(purged)+len(ephemeral)) for _, v := range purged { - out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset()}] = struct{}{} + out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset(), Color: v.GetColor()}] = struct{}{} } for _, v := range ephemeral { - out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset()}] = struct{}{} + out[domain.AccountAssetKey{Account: v.GetAccount(), Asset: v.GetAsset(), Color: v.GetColor()}] = struct{}{} } return out diff --git a/internal/application/usagebuilder/process_audit.go b/internal/application/usagebuilder/process_audit.go index 210764be87..694f65c02c 100644 --- a/internal/application/usagebuilder/process_audit.go +++ b/internal/application/usagebuilder/process_audit.go @@ -370,8 +370,8 @@ func (b *Builder) dispatchMirrorIngest( } // entryVolumeState is the per-audit-entry deduplication scratchpad. Each set -// records the (account, asset) tuples already applied to their respective -// counter for the current audit entry. VolumeCount, CounterEphemeralEvicted +// records the (account, asset, color) tuples already applied to their +// respective counter for the current audit entry. VolumeCount, CounterEphemeralEvicted // and CounterTransientUsed are per-batch cardinality deltas: a tuple that // appears in multiple orders of the same batch (e.g. a shared "bank:main" // account touched by three transactions) must contribute at most once to @@ -384,11 +384,16 @@ type entryVolumeState struct { } // volumeSetKey mirrors state.volumeSetKey — kept local to avoid crossing -// package boundaries just for a triple of strings. +// package boundaries just for a tuple of strings. Color is part of the volume +// identity (volumes are keyed by (account, asset, color) in both the FSM and the +// proto model), so it MUST be in the dedup key: two color buckets of the same +// (account, asset) within one audit entry are distinct volumes and each must +// count once, else VolumeCount / EphemeralEvictedCount undercount colored rows. type volumeSetKey struct { ledger string account string asset string + color string } func newEntryVolumeState() *entryVolumeState { @@ -407,7 +412,7 @@ func newEntryVolumeState() *entryVolumeState { // of the same batch would be counted N times. func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batchState, entry *entryVolumeState) { for _, v := range ann.newKept { - k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset(), color: v.GetColor()} if _, ok := entry.seenNewKept[k]; ok { continue } @@ -421,7 +426,7 @@ func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batc // event) contribute. The pre-EN-1420 EphemeralEvictedCount tallied // every log-level eviction, not just pure ephemeral tuples. for _, v := range ann.purged { - k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset(), color: v.GetColor()} if _, ok := entry.seenPurged[k]; ok { continue } @@ -431,7 +436,7 @@ func applyVolumeAnnotations(ledger string, ann logVolumeAnnotations, state *batc } for _, v := range ann.ephemeral { - k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset()} + k := volumeSetKey{ledger: ledger, account: v.GetAccount(), asset: v.GetAsset(), color: v.GetColor()} if _, ok := entry.seenEphemeral[k]; ok { continue } From ee4c99811f07147b24fdd1267d6557b9670e0a71 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 17 Jul 2026 10:01:06 +0200 Subject: [PATCH 32/35] docs(EN-1334): sync openapi LedgerStats schema with the actual stats DTO The LedgerStats schema declared only accountCount (required but never emitted) and transactionCount, while the handler serializes nine usage counters. Replace the properties with the nine fields the DTO actually returns (transactionCount, volumeCount, referenceCount, postingCount, logCount, ephemeralEvictedCount, transientUsedCount, revertCount, numscriptExecutionCount), drop accountCount, and update the endpoint + api-comparison descriptions. --- docs/technical/contributing/api-comparison.md | 4 +- openapi.yml | 54 ++++++++++++++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/technical/contributing/api-comparison.md b/docs/technical/contributing/api-comparison.md index 86f55c2702..cee1df293a 100644 --- a/docs/technical/contributing/api-comparison.md +++ b/docs/technical/contributing/api-comparison.md @@ -762,7 +762,7 @@ Read endpoints comparison with the original ledger: | `GET /v3/{ledgerName}/accounts/{address}/volumes` | ❌ | ✅ | Get account volumes | | `GET /v3/{ledgerName}/volumes` | ✅ | ✅ | Aggregate volumes (per-asset, supports prefix filtering) | | `GET /v3/{ledgerName}/logs` | ✅ | ✅ | List per-ledger logs. Supports `?after=` for pagination | -| `GET /v3/{ledgerName}/stats` | ✅ | ✅ | Ledger statistics (account + transaction count) | +| `GET /v3/{ledgerName}/stats` | ✅ | ✅ | Ledger usage statistics (transaction, volume, reference, posting, log, revert, Numscript-execution, ephemeral-evicted and transient-used counts) | | `GET /v3/{ledgerName}` | ✅ | ✅ | Get ledger info | | `POST /v3/{ledgerName}/promote` | ✅ | ❌ | Promote mirror ledger to normal mode | | `GET /v3/` | ✅ | ✅ | List all ledgers | @@ -880,7 +880,7 @@ The POC provides a gRPC API for internal service communication (Raft node forwar | `Discovery` | Return server capabilities (response signing config) and build info (`ServerInfo`: version, commit, build date, Go version) | ✅ | | `AnalyzeAccounts` | Analyze accounts and suggest Chart of Accounts | ✅ | | `GetIndexStatus` | Read index builder progress (lag, file size) | ✅ | -| `GetLedgerStats` | Get aggregate statistics (account + transaction count) | ✅ | +| `GetLedgerStats` | Get aggregate usage statistics (transaction, volume, reference, posting, log, revert, Numscript-execution, ephemeral-evicted and transient-used counts) | ✅ | | `AggregateVolumes` | Per-asset aggregated volumes for filtered accounts | ✅ | | `InspectIndex` | Inspect metadata index (distinct values, facets, summary) | ✅ | diff --git a/openapi.yml b/openapi.yml index 346d2c5ac9..4086313ebf 100644 --- a/openapi.yml +++ b/openapi.yml @@ -288,7 +288,7 @@ paths: /v3/{ledgerName}/stats: get: summary: Get ledger statistics - description: Returns aggregate statistics (account count, transaction count) for a ledger. Requires `ledger:read` scope. + description: Returns aggregate usage statistics (transaction, volume, reference, posting, log, revert and Numscript-execution counts, plus ephemeral-evicted and transient-used volume tallies) for a ledger. Requires `ledger:read` scope. operationId: getLedgerStats tags: - Ledgers @@ -3663,19 +3663,61 @@ components: LedgerStats: type: object required: - - accountCount - transactionCount + - volumeCount + - referenceCount + - postingCount + - logCount + - ephemeralEvictedCount + - transientUsedCount + - revertCount + - numscriptExecutionCount properties: - accountCount: + transactionCount: type: integer format: uint64 minimum: 0 - description: Number of accounts in the ledger - transactionCount: + description: Number of transactions in the ledger + volumeCount: type: integer format: uint64 minimum: 0 - description: Number of transactions in the ledger + description: Number of persisted (account, asset, color) volume rows + referenceCount: + type: integer + format: uint64 + minimum: 0 + description: Number of transaction references registered in the ledger + postingCount: + type: integer + format: uint64 + minimum: 0 + description: Total number of postings across all transactions + logCount: + type: integer + format: uint64 + minimum: 0 + description: Number of log entries in the ledger + ephemeralEvictedCount: + type: integer + format: uint64 + minimum: 0 + description: Number of ephemeral/draining volumes evicted from storage at commit + transientUsedCount: + type: integer + format: uint64 + minimum: 0 + description: Number of transient volumes used across proposals + revertCount: + type: integer + format: uint64 + minimum: 0 + description: Number of reverted transactions in the ledger + numscriptExecutionCount: + type: integer + format: uint64 + minimum: 0 + description: Number of Numscript executions in the ledger Account: type: object From 2e92c8bdf473b3c8cf24d3791c31bfdd5b9ff3a6 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 17 Jul 2026 10:12:48 +0200 Subject: [PATCH 33/35] test(EN-1334): integration coverage for processAuditEntries ingestion pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds table-driven tests feeding synthetic AuditEntry/AuditItem/Log/AppliedProposal fixtures through Builder.processAuditEntries against a real temp-dir usagestore, covering the previously-untested path above commitBatch: the create/revert/mirror dispatch fan-out, per-audit-entry volume dedup (shared account counted once; distinct colors counted independently — regression guard for the color-dedup fix), failed-proposal skip, LogSequence==0 skip, transient-volume tally, batch boundaries + cursor advance + idempotent EOF resume. Addresses the reviewer's no-coverage finding; matters because usagestore has no checker backstop. --- .../process_audit_integration_test.go | 800 ++++++++++++++++++ 1 file changed, 800 insertions(+) create mode 100644 internal/application/usagebuilder/process_audit_integration_test.go diff --git a/internal/application/usagebuilder/process_audit_integration_test.go b/internal/application/usagebuilder/process_audit_integration_test.go new file mode 100644 index 0000000000..c50a7d9129 --- /dev/null +++ b/internal/application/usagebuilder/process_audit_integration_test.go @@ -0,0 +1,800 @@ +package usagebuilder + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/metric/noop" + + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + "github.com/formancehq/ledger/v3/internal/proto/auditpb" + "github.com/formancehq/ledger/v3/internal/proto/commonpb" + "github.com/formancehq/ledger/v3/internal/proto/proposalpb" + "github.com/formancehq/ledger/v3/internal/proto/raftcmdpb" + "github.com/formancehq/ledger/v3/internal/storage/dal" + "github.com/formancehq/ledger/v3/internal/storage/usagestore" +) + +// --------------------------------------------------------------------------- +// Fixture-seeding harness +// +// processAuditEntries reads, per audit sequence, from the PRIMARY pebble store: +// - the auditpb.AuditEntry ([ZoneCold][SubColdAudit][seq BE8]) +// - its auditpb.AuditItem(s) ([ZoneCold][SubColdAuditItem][seq BE8][idx BE4]) +// - the proposalpb.AppliedProposal ([ZoneCold][SubColdAppliedProposal][seq BE8]) +// - the commonpb.LedgerLog per item ([ZoneCold][SubColdLog][logSeq BE8]) via readLog +// +// The FSM write helpers that produce these rows (state.batch.go) are +// unexported, so we mirror their key layouts here with the DAL key builder — +// the same layouts query/audit.go, query/applied_proposal.go and query/log.go +// read back. Only structurally-sufficient fields are set: processAuditEntries +// does not verify the hash chain (only the checker does), so a minimal but +// valid (Sequence / Success / items referencing valid log sequences) fixture +// drives every path. +// +// NOTE: query.ReadAppliedProposal returns domain.ErrNotFound when the row is +// absent, which processAuditEntries treats as fatal — so every SUCCESS audit +// entry must have a matching AppliedProposal row (possibly empty). +// --------------------------------------------------------------------------- + +func newUsageTestStores(t *testing.T) (*dal.Store, *usagestore.Store) { + t.Helper() + + ctx := logging.TestingContext() + logger := logging.FromContext(ctx) + meter := noop.NewMeterProvider().Meter("test") + + primary, err := dal.NewStore(t.TempDir(), logger, meter, dal.DefaultConfig()) + require.NoError(t, err) + t.Cleanup(func() { _ = primary.Close() }) + + usage, err := usagestore.New(t.TempDir(), logging.NopZap(), usagestore.DefaultConfig()) + require.NoError(t, err) + t.Cleanup(func() { _ = usage.Close() }) + + return primary, usage +} + +func usageColdAuditKey(seq uint64) []byte { + return dal.NewKeyBuilder(). + PutZonePrefix(dal.ZoneCold, dal.SubColdAudit). + PutUint64(seq). + Build() +} + +func usageColdAuditItemKey(seq uint64, idx uint32) []byte { + return dal.NewKeyBuilder(). + PutZonePrefix(dal.ZoneCold, dal.SubColdAuditItem). + PutUint64(seq). + PutUint32(idx). + Build() +} + +func usageColdAppliedProposalKey(seq uint64) []byte { + return dal.NewKeyBuilder(). + PutZonePrefix(dal.ZoneCold, dal.SubColdAppliedProposal). + PutUint64(seq). + Build() +} + +func usageColdLogKey(seq uint64) []byte { + return dal.NewKeyBuilder(). + PutZonePrefix(dal.ZoneCold, dal.SubColdLog). + PutUint64(seq). + Build() +} + +// touchedVolume builds a (account, asset, color) volume identity tuple. +func touchedVolume(account, asset, color string) *commonpb.TouchedVolume { + return &commonpb.TouchedVolume{Account: account, Asset: asset, Color: color} +} + +// usagePosting builds a posting for the transaction payload — the posting +// count is derived from len(Transaction.Postings) on the produced log. +func usagePosting(source, destination, asset string, amount uint64) *commonpb.Posting { + return &commonpb.Posting{ + Source: source, + Destination: destination, + Asset: asset, + Amount: commonpb.NewUint256FromUint64(amount), + } +} + +// createdTxLog builds an Apply log carrying a CreatedTransaction plus the three +// disjoint volume-annotation lists that live on LedgerLog directly (what +// readLog / applyVolumeAnnotations consume). +func createdTxLog( + seq uint64, + ledger string, + ts *commonpb.Timestamp, + postings []*commonpb.Posting, + newKept, purged, ephemeral []*commonpb.TouchedVolume, +) *commonpb.Log { + return &commonpb.Log{ + Sequence: seq, + Payload: &commonpb.LogPayload{ + Type: &commonpb.LogPayload_Apply{ + Apply: &commonpb.ApplyLedgerLog{ + LedgerName: ledger, + Log: &commonpb.LedgerLog{ + Id: seq, + NewKeptVolumes: newKept, + PurgedVolumes: purged, + EphemeralVolumes: ephemeral, + Data: &commonpb.LedgerLogPayload{ + Payload: &commonpb.LedgerLogPayload_CreatedTransaction{ + CreatedTransaction: &commonpb.CreatedTransaction{ + Transaction: &commonpb.Transaction{ + Id: seq, + Postings: postings, + Timestamp: ts, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// revertedTxLog builds an Apply log carrying a RevertedTransaction plus the +// volume-annotation lists. +func revertedTxLog( + seq uint64, + ledger string, + ts *commonpb.Timestamp, + postings []*commonpb.Posting, + newKept, purged, ephemeral []*commonpb.TouchedVolume, +) *commonpb.Log { + return &commonpb.Log{ + Sequence: seq, + Payload: &commonpb.LogPayload{ + Type: &commonpb.LogPayload_Apply{ + Apply: &commonpb.ApplyLedgerLog{ + LedgerName: ledger, + Log: &commonpb.LedgerLog{ + Id: seq, + NewKeptVolumes: newKept, + PurgedVolumes: purged, + EphemeralVolumes: ephemeral, + Data: &commonpb.LedgerLogPayload{ + Payload: &commonpb.LedgerLogPayload_RevertedTransaction{ + RevertedTransaction: &commonpb.RevertedTransaction{ + RevertTransaction: &commonpb.Transaction{ + Id: seq, + Postings: postings, + Timestamp: ts, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// createTxOrder wraps a CreateTransactionOrder in an Order the way the FSM +// serializes it into an AuditItem (raftcmdpb.Order → SerializedOrder). +func createTxOrder(ledger string, order *raftcmdpb.CreateTransactionOrder) *raftcmdpb.Order { + return &raftcmdpb.Order{ + Type: &raftcmdpb.Order_LedgerScoped{ + LedgerScoped: &raftcmdpb.LedgerScopedOrder{ + Ledger: ledger, + Payload: &raftcmdpb.LedgerScopedOrder_Apply{ + Apply: &raftcmdpb.LedgerApplyOrder{ + Data: &raftcmdpb.LedgerApplyOrder_CreateTransaction{CreateTransaction: order}, + }, + }, + }, + }, + } +} + +// revertTxOrder wraps a RevertTransactionOrder in an Order. +func revertTxOrder(ledger string, order *raftcmdpb.RevertTransactionOrder) *raftcmdpb.Order { + return &raftcmdpb.Order{ + Type: &raftcmdpb.Order_LedgerScoped{ + LedgerScoped: &raftcmdpb.LedgerScopedOrder{ + Ledger: ledger, + Payload: &raftcmdpb.LedgerScopedOrder_Apply{ + Apply: &raftcmdpb.LedgerApplyOrder{ + Data: &raftcmdpb.LedgerApplyOrder_RevertTransaction{RevertTransaction: order}, + }, + }, + }, + }, + } +} + +// mirrorCreatedOrder wraps a MirrorIngest carrying a created-transaction entry. +func mirrorCreatedOrder(ledger, reference string) *raftcmdpb.Order { + return &raftcmdpb.Order{ + Type: &raftcmdpb.Order_LedgerScoped{ + LedgerScoped: &raftcmdpb.LedgerScopedOrder{ + Ledger: ledger, + Payload: &raftcmdpb.LedgerScopedOrder_MirrorIngest{ + MirrorIngest: &raftcmdpb.MirrorIngestOrder{ + Entry: &raftcmdpb.MirrorLogEntry{ + Data: &raftcmdpb.MirrorLogEntry_CreatedTransaction{ + CreatedTransaction: &raftcmdpb.MirrorCreatedTransaction{Reference: reference}, + }, + }, + }, + }, + }, + }, + } +} + +// seedAuditItem is a single order in a synthetic audit entry: the order to +// serialize, the log sequence it produced (0 → skipped/non-log-producing), and +// the log to persist at that sequence (nil → no log row). +type seedAuditItem struct { + order *raftcmdpb.Order + logSeq uint64 + log *commonpb.Log +} + +// seedAuditEntry describes one synthetic audit entry to write into the primary +// store. success=false stages a failed proposal (GetSuccess() == nil, no +// AppliedProposal). transientVolumes stages an AppliedProposal.TransientVolumes +// map keyed by ledger. +type seedAuditEntry struct { + seq uint64 + success bool + items []seedAuditItem + transientVolumes map[string][]*commonpb.TouchedVolume +} + +// seedAuditData writes the full fixture (audit entries, items, applied +// proposals and logs) into the primary store in a single committed batch. +func seedAuditData(t *testing.T, store *dal.Store, entries []seedAuditEntry) { + t.Helper() + + batch := store.OpenWriteSession() + + for _, e := range entries { + auditEntry := &auditpb.AuditEntry{Sequence: e.seq} + if e.success { + auditEntry.Outcome = &auditpb.AuditEntry_Success{Success: &auditpb.AuditSuccess{}} + } else { + auditEntry.Outcome = &auditpb.AuditEntry_Failure{Failure: &auditpb.AuditFailure{}} + } + + require.NoError(t, batch.SetProto(usageColdAuditKey(e.seq), auditEntry)) + + // Only success entries carry an AppliedProposal — a failed proposal + // leaves a gap (ReadAppliedProposal → ErrNotFound), which is exactly + // the branch processAuditEntries skips via GetSuccess() == nil. + if e.success { + proposal := &proposalpb.AppliedProposal{Sequence: e.seq} + if len(e.transientVolumes) > 0 { + proposal.TransientVolumes = make(map[string]*proposalpb.TouchedVolumeList, len(e.transientVolumes)) + for ledger, vols := range e.transientVolumes { + proposal.TransientVolumes[ledger] = &proposalpb.TouchedVolumeList{Volumes: vols} + } + } + + require.NoError(t, batch.SetProto(usageColdAppliedProposalKey(e.seq), proposal)) + } + + for idx, item := range e.items { + raw, err := item.order.MarshalVT() + require.NoError(t, err) + + auditItem := &auditpb.AuditItem{ + OrderIndex: uint32(idx), + SerializedOrder: raw, + LogSequence: item.logSeq, + } + require.NoError(t, batch.SetProto(usageColdAuditItemKey(e.seq, uint32(idx)), auditItem)) + + if item.log != nil { + require.NoError(t, batch.SetProto(usageColdLogKey(item.logSeq), item.log)) + } + } + } + + require.NoError(t, batch.Commit()) +} + +// newTestBuilder constructs a Builder wired to the two stores with the given +// batch size (uses a no-op logger). +func newTestBuilder(primary *dal.Store, usage *usagestore.Store, batchSize int) *Builder { + return &Builder{ + pebbleStore: primary, + usageStore: usage, + logger: logging.NopZap(), + batchSize: batchSize, + } +} + +// --------------------------------------------------------------------------- +// Table-driven end-to-end tests for the ingestion pipeline. +// --------------------------------------------------------------------------- + +func TestProcessAuditEntries_Dispatch(t *testing.T) { + t.Parallel() + + const ledger = "l1" + + ts := &commonpb.Timestamp{Data: 1234} + + type wantCounter struct { + id byte + value uint64 + } + + type wantTemplate struct { + name string + count uint64 + lastUsed uint64 + } + + tests := []struct { + name string + entries []seedAuditEntry + wantCursor uint64 + wantCounters []wantCounter + zeroCounters []byte // counters expected to be absent/zero + wantTemplates []wantTemplate + }{ + { + // Case 1: single scripted CreateTransaction with a reference and one + // new-kept volume — posting/reference/numscript/volume/template all fire. + name: "single_create_transaction_scripted_with_reference", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Reference: "ref-1", + Timestamp: ts, + NumscriptReference: &raftcmdpb.NumscriptReference{Name: "payout"}, + }), + logSeq: 10, + log: createdTxLog(10, ledger, ts, + []*commonpb.Posting{usagePosting("world", "alice", "USD", 100)}, + []*commonpb.TouchedVolume{touchedVolume("alice", "USD", "")}, + nil, nil), + }}, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + {usagestore.CounterPosting, 1}, + {usagestore.CounterReference, 1}, + {usagestore.CounterNumscriptExecution, 1}, + {usagestore.CounterVolume, 1}, + }, + zeroCounters: []byte{usagestore.CounterRevert, usagestore.CounterEphemeralEvicted}, + wantTemplates: []wantTemplate{{"payout", 1, 1234}}, + }, + { + // Case 1b: a non-scripted create with no reference contributes only + // posting + volume; no reference / numscript / template. + name: "single_create_transaction_plain_no_reference", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "bob", "USD", 5)}, + }), + logSeq: 10, + log: createdTxLog(10, ledger, nil, + []*commonpb.Posting{usagePosting("world", "bob", "USD", 5)}, + []*commonpb.TouchedVolume{touchedVolume("bob", "USD", "")}, + nil, nil), + }}, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + {usagestore.CounterPosting, 1}, + {usagestore.CounterVolume, 1}, + }, + zeroCounters: []byte{usagestore.CounterReference, usagestore.CounterNumscriptExecution, usagestore.CounterRevert}, + }, + { + // Case 2: RevertTransaction — revert counter + posting + the + // reversal's purged volume (volume -1, ephemeral-evicted +1). + name: "revert_transaction", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: revertTxOrder(ledger, &raftcmdpb.RevertTransactionOrder{TransactionId: 7}), + logSeq: 10, + log: revertedTxLog(10, ledger, ts, + []*commonpb.Posting{usagePosting("alice", "world", "USD", 100)}, + nil, + []*commonpb.TouchedVolume{touchedVolume("alice", "USD", "")}, + nil), + }}, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + {usagestore.CounterRevert, 1}, + {usagestore.CounterPosting, 1}, + {usagestore.CounterEphemeralEvicted, 1}, + }, + // One purged volume: CounterVolume was decremented from 0 → clamped to 0. + zeroCounters: []byte{usagestore.CounterVolume, usagestore.CounterReference}, + }, + { + // Case 3: MirrorIngest created-transaction — posting + reference + + // volume, but NO numscript/template (client metadata doesn't cross + // the mirror wire). + name: "mirror_ingest_created_transaction", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: mirrorCreatedOrder(ledger, "mirror-ref"), + logSeq: 10, + log: createdTxLog(10, ledger, ts, + []*commonpb.Posting{usagePosting("world", "carol", "USD", 3)}, + []*commonpb.TouchedVolume{touchedVolume("carol", "USD", "")}, + nil, nil), + }}, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + {usagestore.CounterPosting, 1}, + {usagestore.CounterReference, 1}, + {usagestore.CounterVolume, 1}, + }, + zeroCounters: []byte{usagestore.CounterNumscriptExecution, usagestore.CounterRevert}, + }, + { + // Case 4: multi-order dedup within one audit entry — a shared + // (account, asset) touched by three orders contributes to + // CounterVolume exactly once (entryVolumeState dedup). + name: "multi_order_shared_volume_dedup_within_entry", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{ + { + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 1)}, + }), + logSeq: 10, + log: createdTxLog(10, ledger, nil, + []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 1)}, + []*commonpb.TouchedVolume{touchedVolume("bank:main", "USD", "")}, + nil, nil), + }, + { + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 2)}, + }), + logSeq: 11, + log: createdTxLog(11, ledger, nil, + []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 2)}, + []*commonpb.TouchedVolume{touchedVolume("bank:main", "USD", "")}, + nil, nil), + }, + { + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 3)}, + }), + logSeq: 12, + log: createdTxLog(12, ledger, nil, + []*commonpb.Posting{usagePosting("world", "bank:main", "USD", 3)}, + []*commonpb.TouchedVolume{touchedVolume("bank:main", "USD", "")}, + nil, nil), + }, + }, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + // Postings are per-event: 3 orders → 3. + {usagestore.CounterPosting, 3}, + // Volume is per-entry cardinality: shared tuple counts once. + {usagestore.CounterVolume, 1}, + }, + }, + { + // Case 5: color dedup within one audit entry (regression guard) — + // two DISTINCT colors of the same (account, asset) each count + // independently. Two new-kept color buckets → CounterVolume == 2; + // two purged color buckets → CounterEphemeralEvicted == 2. + name: "distinct_colors_same_account_count_independently", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "vault", "USD", 1)}, + }), + logSeq: 10, + log: createdTxLog(10, ledger, nil, + []*commonpb.Posting{usagePosting("world", "vault", "USD", 1)}, + []*commonpb.TouchedVolume{ + touchedVolume("vault", "USD", "red"), + touchedVolume("vault", "USD", "blue"), + }, + []*commonpb.TouchedVolume{ + touchedVolume("vault", "EUR", "red"), + touchedVolume("vault", "EUR", "blue"), + }, + nil), + }}, + }}, + wantCursor: 1, + wantCounters: []wantCounter{ + {usagestore.CounterPosting, 1}, + // 2 new-kept color buckets (+2), 2 purged color buckets (-2) → 0. + {usagestore.CounterVolume, 0}, + // Both purged color buckets are evictions → 2. + {usagestore.CounterEphemeralEvicted, 2}, + }, + }, + { + // Case 6: failed proposal contributes nothing. A success entry + // alongside it proves processing continues and the cursor still + // advances past the failure. + name: "failed_proposal_skipped", + entries: []seedAuditEntry{ + {seq: 1, success: false}, + { + seq: 2, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "dave", "USD", 9)}, + }), + logSeq: 10, + log: createdTxLog(10, ledger, nil, + []*commonpb.Posting{usagePosting("world", "dave", "USD", 9)}, + nil, nil, nil), + }}, + }, + }, + wantCursor: 2, + wantCounters: []wantCounter{{usagestore.CounterPosting, 1}}, + }, + { + // Case 8: LogSequence == 0 item is skipped (no log-producing order). + // The entry still advances the cursor but contributes no counters. + name: "log_sequence_zero_item_skipped", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "eve", "USD", 1)}, + }), + logSeq: 0, // idempotent replay / non-log-producing → skip + log: nil, + }}, + }}, + wantCursor: 1, + zeroCounters: []byte{usagestore.CounterPosting, usagestore.CounterVolume}, + }, + { + // TransientVolumes on the AppliedProposal → CounterTransientUsed. + name: "transient_volumes_from_applied_proposal", + entries: []seedAuditEntry{{ + seq: 1, + success: true, + transientVolumes: map[string][]*commonpb.TouchedVolume{ + ledger: { + touchedVolume("tmp:1", "USD", ""), + touchedVolume("tmp:2", "USD", ""), + }, + }, + }}, + wantCursor: 1, + wantCounters: []wantCounter{{usagestore.CounterTransientUsed, 2}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + primary, usage := newUsageTestStores(t) + seedAuditData(t, primary, tc.entries) + + b := newTestBuilder(primary, usage, 200) + + cursor, err := b.processAuditEntries(context.Background(), 0, time.Time{}) + require.NoError(t, err) + assert.Equal(t, tc.wantCursor, cursor, "cursor must advance to the last audit sequence") + assert.Equal(t, tc.wantCursor, b.LastProcessedAuditSequence(), "atomic hint must mirror the cursor") + + for _, wc := range tc.wantCounters { + got, err := usage.GetCounter(ledger, wc.id) + require.NoError(t, err) + assert.Equalf(t, wc.value, got, "counter %#x", wc.id) + } + + for _, id := range tc.zeroCounters { + got, err := usage.GetCounter(ledger, id) + require.NoError(t, err) + assert.Equalf(t, uint64(0), got, "counter %#x must be zero", id) + } + + for _, wt := range tc.wantTemplates { + got, err := usage.GetTemplateUsage(ledger, wt.name) + require.NoError(t, err) + require.NotNilf(t, got, "template %q must exist", wt.name) + assert.Equalf(t, wt.count, got.GetCount(), "template %q count", wt.name) + assert.Equalf(t, wt.lastUsed, got.GetLastUsed().GetData(), "template %q lastUsed", wt.name) + } + + // The persisted progress cursor must match the returned cursor. + progress, err := usage.ReadProgress() + require.NoError(t, err) + assert.Equal(t, tc.wantCursor, progress, "persisted progress must equal the cursor") + }) + } +} + +// TestProcessAuditEntries_SkippedCreateDoesNotCount guards the isCreatedTx gate: +// a CreateTransaction that produced an OrderSkipped log (no CreatedTransaction / +// RevertedTransaction payload) must contribute no reference / numscript / +// template counters even though the order carried them. +func TestProcessAuditEntries_SkippedCreateDoesNotCount(t *testing.T) { + t.Parallel() + + const ledger = "l1" + + primary, usage := newUsageTestStores(t) + + // A log with no CreatedTransaction/RevertedTransaction payload models a + // skipped order: readLog reports isCreatedTx == false. + skippedLog := &commonpb.Log{ + Sequence: 10, + Payload: &commonpb.LogPayload{ + Type: &commonpb.LogPayload_Apply{ + Apply: &commonpb.ApplyLedgerLog{ + LedgerName: ledger, + Log: &commonpb.LedgerLog{Id: 10}, + }, + }, + }, + } + + seedAuditData(t, primary, []seedAuditEntry{{ + seq: 1, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Reference: "ref-1", + NumscriptReference: &raftcmdpb.NumscriptReference{Name: "payout"}, + }), + logSeq: 10, + log: skippedLog, + }}, + }}) + + b := newTestBuilder(primary, usage, 200) + + cursor, err := b.processAuditEntries(context.Background(), 0, time.Time{}) + require.NoError(t, err) + require.Equal(t, uint64(1), cursor) + + for _, id := range []byte{ + usagestore.CounterReference, + usagestore.CounterNumscriptExecution, + usagestore.CounterPosting, + usagestore.CounterVolume, + } { + got, err := usage.GetCounter(ledger, id) + require.NoError(t, err) + assert.Equalf(t, uint64(0), got, "skipped create must not bump counter %#x", id) + } + + tpl, err := usage.GetTemplateUsage(ledger, "payout") + require.NoError(t, err) + assert.Nil(t, tpl, "skipped create must not record template usage") +} + +// TestProcessAuditEntries_BatchBoundaryAndCursorAdvance covers case 7: with +// batchSize=2 and three entries, all are processed across batches, the returned +// cursor equals the last sequence, and a second call from that cursor is a +// no-op (EOF). +func TestProcessAuditEntries_BatchBoundaryAndCursorAdvance(t *testing.T) { + t.Parallel() + + const ledger = "l1" + + primary, usage := newUsageTestStores(t) + + var entries []seedAuditEntry + for seq := uint64(1); seq <= 3; seq++ { + logSeq := 10 + seq + entries = append(entries, seedAuditEntry{ + seq: seq, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "acc", "USD", seq)}, + }), + logSeq: logSeq, + log: createdTxLog(logSeq, ledger, nil, + []*commonpb.Posting{usagePosting("world", "acc", "USD", seq)}, + nil, nil, nil), + }}, + }) + } + + seedAuditData(t, primary, entries) + + b := newTestBuilder(primary, usage, 2) // batchSize=2 → two commits (2 then 1) + + cursor, err := b.processAuditEntries(context.Background(), 0, time.Time{}) + require.NoError(t, err) + require.Equal(t, uint64(3), cursor, "all three entries processed across batch boundaries") + + got, err := usage.GetCounter(ledger, usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(3), got, "one posting per entry across both batches") + + // Persisted progress reflects the full drain. + progress, err := usage.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(3), progress) + + // A second call from the advanced cursor is a no-op: nothing new to read, + // clean EOF, cursor unchanged. + cursor2, err := b.processAuditEntries(context.Background(), cursor, time.Time{}) + require.NoError(t, err) + assert.Equal(t, uint64(3), cursor2, "re-running from the head is an idempotent no-op") + + got, err = usage.GetCounter(ledger, usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(3), got, "no-op pass must not double-count") +} + +// TestProcessAuditEntries_ResumeFromCursorSkipsProcessed verifies the cursor +// filter: starting from a non-zero cursor skips already-consumed entries and +// only folds the tail. +func TestProcessAuditEntries_ResumeFromCursorSkipsProcessed(t *testing.T) { + t.Parallel() + + const ledger = "l1" + + primary, usage := newUsageTestStores(t) + + var entries []seedAuditEntry + for seq := uint64(1); seq <= 3; seq++ { + logSeq := 100 + seq + entries = append(entries, seedAuditEntry{ + seq: seq, + success: true, + items: []seedAuditItem{{ + order: createTxOrder(ledger, &raftcmdpb.CreateTransactionOrder{ + Postings: []*commonpb.Posting{usagePosting("world", "acc", "USD", seq)}, + }), + logSeq: logSeq, + log: createdTxLog(logSeq, ledger, nil, + []*commonpb.Posting{usagePosting("world", "acc", "USD", seq)}, + nil, nil, nil), + }}, + }) + } + + seedAuditData(t, primary, entries) + + b := newTestBuilder(primary, usage, 200) + + // Resume from cursor=2: only entry seq 3 should be folded. + cursor, err := b.processAuditEntries(context.Background(), 2, time.Time{}) + require.NoError(t, err) + assert.Equal(t, uint64(3), cursor) + + got, err := usage.GetCounter(ledger, usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(1), got, "only the single post-cursor entry must be folded") +} From e85936115c0383f5a53cd8047f4309cf49c669f9 Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 17 Jul 2026 10:50:20 +0200 Subject: [PATCH 34/35] fix(EN-1334): wire usagestore rollback self-heal + address review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - builder: wire the documented usagestore.Reset() rollback self-heal. boot() AND tick() now detect a primary-store rollback beneath the usage cursor (cursor > audit head) via a shared resetIfRolledBack helper, wipe the projection + cursor, and replay from 0 — previously the docs promised this but no code path called Reset(), leaving the projection permanently over-counted on a restore-behind-cursor (no checker backstop for the peer store). Add a focused test. - AGENTS.md invariant #8: EphemeralEvictedCount/TransientUsedCount moved to usagestore (LedgerBoundaries reserves tags 3-10) — correct the stale text that said they 'remain on LedgerBoundaries'. - openapi + controller: document that checkpoint-scoped /stats returns 0 for usage-derived counters (not checkpoint-aware) so a client reads 0 as 'unavailable at this checkpoint'. - e2e(s3) restore tests: migrate the missed color-of-money volume API (Volumes[asset] -> FindVolume(asset,"")) and drop the removed LedgerStats.GetMetadataCount assertion, unbreaking the e2e,s3 build. --- AGENTS.md | 2 +- .../application/ctrl/controller_default.go | 5 +- internal/application/usagebuilder/builder.go | 59 +++++++++++++++--- .../process_audit_integration_test.go | 60 +++++++++++++++++++ openapi.yml | 11 +++- tests/e2e/cluster/restore_reversion_test.go | 2 +- tests/e2e/cluster/restore_test.go | 11 ++-- 7 files changed, 131 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dc70fbe6cb..140a0bc89e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ This document contains rules and conventions for AI agents working on this codeb **Volume preload is load-bearing for the `LedgerLog.new_kept_volumes` / `ephemeral_volumes` split.** Admission MUST preload the current value of every `(account, asset)` volume touched by an order, including postings resolved from numscript execution. The FSM classifies a volume as "newly created" by checking `Old.IsDefined()` (or the zero-placeholder via `isVolumePreloadZero`) at merge time — a silent cache miss on an existing volume produces a false-new classification, mis-routes the tuple between the `new_kept_volumes` / `ephemeral_volumes` / `purged_volumes` per-log lists, and inflates `usagestore.CounterVolume`. Because `usagestore` is a rebuildable peer side-store held **outside** checker scope (invariant #8 verifies only primary-Pebble-store projections), no checker pass would surface the drift; it persists until an explicit usage rebuild. That absence of a backstop makes the preload correctness even more load-bearing, not less. This is not opportunistic (unlike the metadata preload that was removed after the indexbuilder stopped needing it): balance checks, Uint256 arithmetic and numscript resolution all require the current volume value, so the preload is structurally mandatory. If a future refactor considers alleviating volume preload, `VolumeCount` must be re-hosted first (via computed-on-read or a subsystem-side seen-keys table) — see EN-1422 for the design rationale. 7. **Never silently skip a "should not happen" branch** — A branch that is reachable only if an invariant is violated (nil where the contract says non-nil, a state we believe unreachable, a cache miss after a guaranteed preload, etc.) MUST surface a loud signal: `return fmt.Errorf("invariant: ...")` so it bubbles up, or `assert.Unreachable(...)` for SUT-level invariants exercised under antithesis. A silent `return nil` / `continue` on these branches hides real bugs — particularly catastrophic in the FSM apply path, where a no-op desyncs nodes from each other. Branches that represent genuine runtime conditions (cache miss as an expected outcome, stale proposal, deleted entity) keep their soft `return nil`. The distinction is whether the case is *expected* (soft skip OK) or *impossible by design* (must fail loudly). The comment must say *why* the case is impossible so a reader can decide whether to add a hard fail or relax the rule. -8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. Two scope refinements: (a) this applies to the **primary FSM store** — the single store `Check()` opens and walks; **peer secondary stores** (the `readstore` inverted index today; the `usagestore` counters forthcoming with EN-1334) are out of *main-store checker* scope *by construction* since `Check()` never opens them. That scope carve-out is **not** a claim they are integrity-safe: the readstore serves READY indexes directly to business-visible queries with no scan fallback, and its automated detect/drop/rebuild is **not yet wired** (tracked under `EN-1323`), so a corrupted or tampered index is a **current open integrity gap on the peer-store side** — a per-replica rebuild-health concern of the index builder, not an invariant-#8 main-store concern. Out of the main-store checker's mandate, not out of every integrity concern. (b) A primary-store projection may be exempted from a dedicated pass only when it is either (i) deterministically rebuildable from still-retained verified state through a real, *wired* rebuild path — not rebuildable merely in principle — or (ii) purely informational and intentionally carried across restore (cf. `BuildStatus`). These are distinct bases: of the *Known projection gaps* below, `DefaultEnforcementMode` and `SubAttrLedgerMetadata` qualify under (i) via `RebuildDelta`, while `EphemeralEvictedCount` / `TransientUsedCount` qualify under (ii) — they are NOT rebuilt by `RebuildDelta`, so the wired-rebuild basis must not be claimed for them. The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus), and `compareMirrorV2LogID` (stored `LedgerBoundaries.last_mirror_v2_log_id` **==** `max(audited MirrorIngest.v2_log_id)` per ledger — a full equality check, `CHECK_STORE_ERROR_TYPE_MIRROR_V2LOGID_MISMATCH` on ANY divergence; the FSM enforces a contiguous applied prefix so at rest the two must be exactly equal, and the baseline floor is seeded from archived `Boundary` rows now included in the compact baseline snapshot; pre-field clusters are unsupported, no backfill leniency), and `compareSchema` (per-ledger `LedgerInfo.MetadataSchema` vs the replayed CreateLedger.initial_schema + SetMetadataFieldType/RemovedMetadataFieldType logs; under archived chapters the boundary schema is seeded from the baseline checkpoint, which carries LedgerInfo for this purpose), and `compareAccountTypes` (per-ledger `LedgerInfo.AccountTypes` vs the replayed AddAccountType/RemoveAccountType logs, baseline-seeded under archiving like the schema), and `compareLedgerPresence` (the live ledger set in the store must match the audit-derived set both ways: every audit-live ledger — CreateLedger with no later DeleteLedger, or a non-deleted baseline ledger under archiving — must have a *live* stored `LedgerInfo` else `MISSING_LEDGER` (covers an entry deleted outright OR tampered to a soft-deleted tombstone, which the compareSchema/compareAccountTypes loops skip), and every live stored `LedgerInfo` must be audit-backed else `UNAUDITED_LEDGER` (an injected empty ledger row carries no schema/types for the projection passes to flag). The audit-derived set is NEVER seeded from the live store — under archiving it comes from the baseline checkpoint, keeping the check independent of the data it verifies), and `compareReferences` (the SubAttrReference reference→txID uniqueness index vs the references replayed from CreatedTransaction/RevertedTransaction logs, baseline-seeded under archiving; verified both ways — missing, unaudited, and retargeted rows are all flagged; rows for deleted or cleanup-pending ledgers are skipped since they legitimately linger until a covering purge runs deleteLedgerData), and `compareBoundaries` (per-ledger `LedgerBoundaries` vs the checker’s re-derivation: only NextTransactionId/NextLogId are verified here — derived from the replayed logs plus the chain-bound AuditItem order effects (mirror fill-gap advances live on the orders, not the ledger-log stream), baseline-seeded under archiving; the mirror high-water `last_mirror_v2_log_id` is verified separately by `compareMirrorV2LogID`. The per-ledger usage counters — `PostingCount`, `RevertCount`, `NumscriptExecutionCount`, `VolumeCount`, `MetadataCount`, `ReferenceCount` — no longer live on `LedgerBoundaries`: they moved to the `usagestore` peer secondary store (EN-1334) and are out of main-store checker scope **by construction** (per the peer-store carve-out in scope refinement (a) above), their integrity being a peer-store rebuild-health concern rather than an invariant-#8 main-store concern; `EphemeralEvictedCount`/`TransientUsedCount` remain on `LedgerBoundaries` but are intentionally excluded — informational, carried across restore, cf. BuildStatus), and `compareReversions` (the per-ledger reversion bitsets — `ZonePerLedger`/`SubPLReversions`, the projection the FSM's already-reverted gate reads — vs the reverted set derived from baseline tx-row markers plus the replayed RevertedTransaction logs; exact equality both ways — a lost bit re-admits a double revert, an unaudited bit blocks a legitimate one; driven purely by audit-derived state — no persisted marker (pending-cleanup included) can exempt a live ledger, stored rows for non-live ledgers are flagged since DeleteLedger deletes them at apply on both the live path and the replay, and undecodable rows are reported); extend the list as new persisted projections land. **Known projection gaps**: `LedgerBoundaries.EphemeralEvictedCount` / `TransientUsedCount` are excluded from `compareBoundaries` and carried across restore rather than re-derived — informational counters (cf. BuildStatus); `LedgerInfo.DefaultEnforcementMode` and the ledger-metadata attribute (`SubAttrLedgerMetadata`) have no compare pass yet (both are rebuilt on restore by RebuildDelta). +8. **The audit log is the only source of truth — every other persisted dataset is a projection and must be verified by the checker** — Only `AuditEntry` (zone `Cold`, sub `Audit`) is cryptographically bound, via the hash chain that `state.BuildHashedHeaderPayload` + `processing.HashGenerator` produce and `checker.verifyAuditHashChain` verifies on every Check() run. Everything else stored in Pebble — `Log`, `AuditItem`, `AppliedProposal`, `LedgerLog.PurgedVolumes`, attribute caches (`Volume`, `Metadata`, `Transaction`, `Reference`, `Boundary`, etc.), reversion bitsets, idempotency keys, mirror cursors, chapters, bloom filters, signing keys, the read-side index — is a *projection* of orders that already live in the audit chain. Projections are rebuildable from the audit on demand, so we deliberately do NOT extend the hash chain to cover them (refactor over hash binding — see `feedback_audit_is_source_of_truth`). In exchange, **`internal/application/check/checker.go` MUST verify every projection it persists**: re-derive the value the projection should hold by replaying the audit (`ReplayLedgerLog`, `SimulateEphemeralPurge`, `partitionVolumes`, etc.) and compare to what is stored, emitting the matching `CHECK_STORE_ERROR_TYPE_*` event on divergence. A projection that the checker does not verify is a tampering vector — adding a new persisted projection without a matching compare* / collect* pass in the checker is the violation. Two scope refinements: (a) this applies to the **primary FSM store** — the single store `Check()` opens and walks; **peer secondary stores** (the `readstore` inverted index today; the `usagestore` counters forthcoming with EN-1334) are out of *main-store checker* scope *by construction* since `Check()` never opens them. That scope carve-out is **not** a claim they are integrity-safe: the readstore serves READY indexes directly to business-visible queries with no scan fallback, and its automated detect/drop/rebuild is **not yet wired** (tracked under `EN-1323`), so a corrupted or tampered index is a **current open integrity gap on the peer-store side** — a per-replica rebuild-health concern of the index builder, not an invariant-#8 main-store concern. Out of the main-store checker's mandate, not out of every integrity concern. (b) A primary-store projection may be exempted from a dedicated pass only when it is either (i) deterministically rebuildable from still-retained verified state through a real, *wired* rebuild path — not rebuildable merely in principle — or (ii) purely informational and intentionally carried across restore (cf. `BuildStatus`). These are distinct bases: of the *Known projection gaps* below, `DefaultEnforcementMode` and `SubAttrLedgerMetadata` qualify under (i) via `RebuildDelta` (`BuildStatus` on the index registry is the canonical (ii) example). The current passes are `compareVolumes`, `compareMetadata`, `compareTransactions`, `compareExclusionProjections` (AppliedProposal.TransientVolumes + LedgerLog.PurgedVolumes), `checkReversionInvariants`, `verifySealingHash`, `compareIdempotencyOutcomes` (frozen idempotency outcomes in SubIdempKeys vs the hash-chained AuditFailure/AuditSuccess that wrote them — the failure kind is re-derived from the chain-bound reason via `domain.KindForReason`, never stored), and `compareIndexes` (SubAttrIndex registry vs CreateIndex/DropIndex/RemovedMetadataFieldType/DeleteLedger logs — covers presence + identity; BuildStatus is intentionally excluded because it is purely informational on the cluster-wide registry entry — queries gate on the per-replica `IndexVersionState.CurrentVersion`, not on BuildStatus), and `compareMirrorV2LogID` (stored `LedgerBoundaries.last_mirror_v2_log_id` **==** `max(audited MirrorIngest.v2_log_id)` per ledger — a full equality check, `CHECK_STORE_ERROR_TYPE_MIRROR_V2LOGID_MISMATCH` on ANY divergence; the FSM enforces a contiguous applied prefix so at rest the two must be exactly equal, and the baseline floor is seeded from archived `Boundary` rows now included in the compact baseline snapshot; pre-field clusters are unsupported, no backfill leniency), and `compareSchema` (per-ledger `LedgerInfo.MetadataSchema` vs the replayed CreateLedger.initial_schema + SetMetadataFieldType/RemovedMetadataFieldType logs; under archived chapters the boundary schema is seeded from the baseline checkpoint, which carries LedgerInfo for this purpose), and `compareAccountTypes` (per-ledger `LedgerInfo.AccountTypes` vs the replayed AddAccountType/RemoveAccountType logs, baseline-seeded under archiving like the schema), and `compareLedgerPresence` (the live ledger set in the store must match the audit-derived set both ways: every audit-live ledger — CreateLedger with no later DeleteLedger, or a non-deleted baseline ledger under archiving — must have a *live* stored `LedgerInfo` else `MISSING_LEDGER` (covers an entry deleted outright OR tampered to a soft-deleted tombstone, which the compareSchema/compareAccountTypes loops skip), and every live stored `LedgerInfo` must be audit-backed else `UNAUDITED_LEDGER` (an injected empty ledger row carries no schema/types for the projection passes to flag). The audit-derived set is NEVER seeded from the live store — under archiving it comes from the baseline checkpoint, keeping the check independent of the data it verifies), and `compareReferences` (the SubAttrReference reference→txID uniqueness index vs the references replayed from CreatedTransaction/RevertedTransaction logs, baseline-seeded under archiving; verified both ways — missing, unaudited, and retargeted rows are all flagged; rows for deleted or cleanup-pending ledgers are skipped since they legitimately linger until a covering purge runs deleteLedgerData), and `compareBoundaries` (per-ledger `LedgerBoundaries` vs the checker’s re-derivation: only NextTransactionId/NextLogId are verified here — derived from the replayed logs plus the chain-bound AuditItem order effects (mirror fill-gap advances live on the orders, not the ledger-log stream), baseline-seeded under archiving; the mirror high-water `last_mirror_v2_log_id` is verified separately by `compareMirrorV2LogID`. The per-ledger usage counters — `PostingCount`, `RevertCount`, `NumscriptExecutionCount`, `VolumeCount`, `MetadataCount`, `ReferenceCount`, `EphemeralEvictedCount`, `TransientUsedCount` — no longer live on `LedgerBoundaries`: `LedgerBoundaries` now reserves tags 3-10 for them and they moved to the `usagestore` peer secondary store (EN-1334 / EN-1420), out of main-store checker scope **by construction** (per the peer-store carve-out in scope refinement (a) above), their integrity being a peer-store rebuild-health concern — reconverged by the online usagebuilder fold + `usagestore.Reset()` on rollback — rather than an invariant-#8 main-store concern), and `compareReversions` (the per-ledger reversion bitsets — `ZonePerLedger`/`SubPLReversions`, the projection the FSM's already-reverted gate reads — vs the reverted set derived from baseline tx-row markers plus the replayed RevertedTransaction logs; exact equality both ways — a lost bit re-admits a double revert, an unaudited bit blocks a legitimate one; driven purely by audit-derived state — no persisted marker (pending-cleanup included) can exempt a live ledger, stored rows for non-live ledgers are flagged since DeleteLedger deletes them at apply on both the live path and the replay, and undecodable rows are reported); extend the list as new persisted projections land. **Known projection gaps**: `LedgerInfo.DefaultEnforcementMode` and the ledger-metadata attribute (`SubAttrLedgerMetadata`) have no compare pass yet (both are rebuilt on restore by RebuildDelta). 9. **Never bypass the FSM coverage gate** — Every cache-attribute read on the FSM hot path MUST go through `Scope.GetX(...)` so the per-order `coverage_bits` admit it. Reading the underlying `Registry.X.KeyStore().M` (or any other parent-cache iterator) directly skips the gate and produces non-deterministic FSM behavior: the gate is what binds the order to the admission-declared preload set, and a direct read silently sees keys the proposer never declared. There is NO documented exception — paths that need to iterate (e.g. cascade-on-delete) MUST either declare the relevant `preload.Needs` upfront, defer the work to a lifecycle path (`batch.deleteLedgerData` + `MarkLedgerForCleanup`), or be rejected at design review. New helpers that scan the parent KeyStore from inside an order/TU handler are the violation, even when wrapped in a method on `WriteSet`. The coverage gate exists precisely so admission's declared key set is the FSM's only legitimate read horizon — under no circumstances should the apply path widen it on the fly. diff --git a/internal/application/ctrl/controller_default.go b/internal/application/ctrl/controller_default.go index 3f4a4088b3..eaef052544 100644 --- a/internal/application/ctrl/controller_default.go +++ b/internal/application/ctrl/controller_default.go @@ -641,7 +641,10 @@ func (ctrl *DefaultController) GetLedgerStats(ctx context.Context, ledgerName st // produce a non-deterministic response for the same checkpoint id. // This is the same trade-off that keeps read-index rebuild scoped to // live state — future work can add checkpointed usage projections if - // clients need them. + // clients need them. The contract is documented on the openapi /stats + // endpoint so a client can read a checkpoint `0` as "not available at + // this checkpoint" rather than a live value; only TransactionCount / + // LogCount are checkpoint-consistent here. if ctrl.historical { return &stats, nil } diff --git a/internal/application/usagebuilder/builder.go b/internal/application/usagebuilder/builder.go index 3baa666495..5ded947908 100644 --- a/internal/application/usagebuilder/builder.go +++ b/internal/application/usagebuilder/builder.go @@ -164,6 +164,13 @@ func (b *Builder) boot(ctx context.Context) error { pebbleLast, sampleErr := b.sampleAuditHead() if sampleErr == nil { b.pebbleLastAuditSeq.Store(pebbleLast) + + // Rollback detection before the catch-up: if the primary store was + // restored beneath the persisted cursor, drop the projection and rewind + // to 0 so the fold below replays into a clean store. + if cursor, err = b.resetIfRolledBack(cursor, pebbleLast); err != nil { + return err + } } b.logger.WithFields(map[string]any{ @@ -211,21 +218,28 @@ func (b *Builder) boot(ctx context.Context) error { return nil } -// tick runs one steady-state iteration: refresh the audit-head gauge, then -// drain any pending audit entries from the persisted cursor forward. +// tick runs one steady-state iteration: refresh the audit-head gauge, detect a +// primary-store rollback, then drain any pending audit entries from the cursor +// forward. // -// No rollback/cursor-ahead re-check is needed: the audit chain is append-only -// and monotone. Audit entries are written only for committed Raft entries -// (Machine.applyProposal), so by Raft leader-completeness a written entry -// survives every leadership change and RestoreCheckpoint only ever advances a -// node that is behind (IsStoreUpToDate gates sync on LastAppliedIndex >= -// SnapshotIndex). The persisted cursor can therefore never legitimately sit -// ahead of the audit head, so there is nothing to rewind. +// In steady state the persisted cursor sits at or behind the audit head: the +// chain is append-only and RestoreCheckpoint normally only advances a node that +// is behind (IsStoreUpToDate gates sync on LastAppliedIndex >= SnapshotIndex). +// But a restore that rolls the primary store back BENEATH the persisted cursor +// leaves the cursor ahead of the audit head; a forward-only fold would then +// no-op and leave the projection permanently over-counted — and, because the +// usagestore is a peer store outside checker scope (invariant #8), nothing would +// surface the drift. resetIfRolledBack wipes the projection + cursor and rewinds +// to 0 so this tick replays into a clean store (docs: usagebuilder.md rollback). func (b *Builder) tick(ctx context.Context) error { cursor := b.lastProcessedAuditSeq.Load() if last, err := b.sampleAuditHead(); err == nil { b.pebbleLastAuditSeq.Store(last) + + if cursor, err = b.resetIfRolledBack(cursor, last); err != nil { + return err + } } _, err := b.processAuditEntries(ctx, cursor, time.Time{}) @@ -233,6 +247,33 @@ func (b *Builder) tick(ctx context.Context) error { return err } +// resetIfRolledBack detects a primary-store rollback beneath the usage cursor — +// the persisted cursor sitting AHEAD of the current audit head — and, when found, +// wipes every counter/template row + the progress cursor (usagestore.Reset) so +// the caller replays the projection from audit sequence 0. It returns the cursor +// to resume from: 0 after a reset, or the unchanged cursor otherwise. A reset +// also rewinds the lastProcessedAuditSeq atomic so external readers observe the +// rewind. This is the online reconvergence path the usagebuilder relies on in +// 3.0 (offline drop-and-rebuild is deferred to 3.1 — see usagebuilder.md). +func (b *Builder) resetIfRolledBack(cursor, auditHead uint64) (uint64, error) { + if cursor <= auditHead { + return cursor, nil + } + + b.logger.WithFields(map[string]any{ + "cursor": cursor, + "auditHead": auditHead, + }).Infof("usage cursor ahead of audit head — primary store rolled back; resetting usage projection and replaying from audit sequence 0") + + if err := b.usageStore.Reset(); err != nil { + return cursor, fmt.Errorf("resetting usage projection after rollback: %w", err) + } + + b.lastProcessedAuditSeq.Store(0) + + return 0, nil +} + // sampleAuditHead opens a short-lived read handle to read the current audit // head. The handle is closed immediately so RestoreCheckpoint's write lock // is not blocked during idle ticks. diff --git a/internal/application/usagebuilder/process_audit_integration_test.go b/internal/application/usagebuilder/process_audit_integration_test.go index c50a7d9129..ffe5ba0fc5 100644 --- a/internal/application/usagebuilder/process_audit_integration_test.go +++ b/internal/application/usagebuilder/process_audit_integration_test.go @@ -317,6 +317,66 @@ func newTestBuilder(primary *dal.Store, usage *usagestore.Store, batchSize int) } } +// TestResetIfRolledBack covers the rollback self-heal wired into boot()/tick(): +// when the persisted usage cursor sits ahead of the audit head (the primary +// store was restored beneath it), the projection + cursor are wiped so the fold +// replays from 0. There is no checker backstop for the usagestore, so this is +// the only reconvergence path — worth pinning. +func TestResetIfRolledBack(t *testing.T) { + t.Parallel() + + t.Run("cursor ahead of audit head wipes the projection and rewinds to 0", func(t *testing.T) { + t.Parallel() + + _, usage := newUsageTestStores(t) + b := &Builder{usageStore: usage, logger: logging.NopZap()} + + // Seed a projection + a non-zero progress cursor (commitBatch writes both). + seed := newBatchState() + seed.addCounter("l1", usagestore.CounterPosting, 42) + require.NoError(t, b.commitBatch(seed, 7)) + b.lastProcessedAuditSeq.Store(7) + + got, err := usage.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + require.Equal(t, uint64(42), got) + + // Audit head (3) sits BELOW the persisted cursor (7) — rollback. + cursor, err := b.resetIfRolledBack(7, 3) + require.NoError(t, err) + assert.Equal(t, uint64(0), cursor, "must rewind to replay from 0") + + got, err = usage.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(0), got, "projection must be wiped") + + progress, err := usage.ReadProgress() + require.NoError(t, err) + assert.Equal(t, uint64(0), progress, "cursor must be cleared") + + assert.Equal(t, uint64(0), b.lastProcessedAuditSeq.Load(), "atomic hint must be rewound") + }) + + t.Run("cursor at or behind audit head is a no-op", func(t *testing.T) { + t.Parallel() + + _, usage := newUsageTestStores(t) + b := &Builder{usageStore: usage, logger: logging.NopZap()} + + seed := newBatchState() + seed.addCounter("l1", usagestore.CounterPosting, 5) + require.NoError(t, b.commitBatch(seed, 2)) + + cursor, err := b.resetIfRolledBack(2, 9) + require.NoError(t, err) + assert.Equal(t, uint64(2), cursor, "cursor unchanged when at/behind the head") + + got, err := usage.GetCounter("l1", usagestore.CounterPosting) + require.NoError(t, err) + assert.Equal(t, uint64(5), got, "projection must be intact") + }) +} + // --------------------------------------------------------------------------- // Table-driven end-to-end tests for the ingestion pipeline. // --------------------------------------------------------------------------- diff --git a/openapi.yml b/openapi.yml index 4086313ebf..6c4af6a01d 100644 --- a/openapi.yml +++ b/openapi.yml @@ -288,7 +288,16 @@ paths: /v3/{ledgerName}/stats: get: summary: Get ledger statistics - description: Returns aggregate usage statistics (transaction, volume, reference, posting, log, revert and Numscript-execution counts, plus ephemeral-evicted and transient-used volume tallies) for a ledger. Requires `ledger:read` scope. + description: >- + Returns aggregate usage statistics (transaction, volume, reference, posting, log, revert + and Numscript-execution counts, plus ephemeral-evicted and transient-used volume tallies) + for a ledger. Requires `ledger:read` scope. + Note: for checkpoint-scoped (historical) reads, only `transactionCount` and `logCount` are + checkpoint-consistent; the usage-derived counters (`postingCount`, `revertCount`, + `numscriptExecutionCount`, `referenceCount`, `ephemeralEvictedCount`, `transientUsedCount`, + `volumeCount`) are sourced from the live usagestore projection, which is not checkpoint-aware, + and are returned as `0` at a checkpoint rather than a live value — treat `0` on a checkpoint + read as "not available at this checkpoint". operationId: getLedgerStats tags: - Ledgers diff --git a/tests/e2e/cluster/restore_reversion_test.go b/tests/e2e/cluster/restore_reversion_test.go index b72748936a..2f5c04aa95 100644 --- a/tests/e2e/cluster/restore_reversion_test.go +++ b/tests/e2e/cluster/restore_reversion_test.go @@ -79,7 +79,7 @@ var _ = Describe("Restore reversion bitset", Ordered, func() { acct, err := actions.GetAccount(ctx, client, ledgerName, account) Expect(err).To(Succeed()) - vol := acct.GetVolumes()["USD"] + vol := acct.FindVolume("USD", "") Expect(vol).ToNot(BeNil(), "%s: %s USD volumes missing", phase, account) Expect(vol.GetInput()).To(Equal("600"), "%s: %s USD input", phase, account) Expect(vol.GetOutput()).To(Equal("100"), "%s: %s USD output", phase, account) diff --git a/tests/e2e/cluster/restore_test.go b/tests/e2e/cluster/restore_test.go index de19c66227..2b13bd3fc4 100644 --- a/tests/e2e/cluster/restore_test.go +++ b/tests/e2e/cluster/restore_test.go @@ -783,7 +783,6 @@ var _ = Describe("Restore", Ordered, func() { Expect(stats.GetLogCount()).To(Equal(uint64(1))) Expect(stats.GetPostingCount()).To(Equal(uint64(1))) Expect(stats.GetVolumeCount()).To(Equal(uint64(2)), "world + founder") - Expect(stats.GetMetadataCount()).To(Equal(uint64(0))) Expect(stats.GetRevertCount()).To(Equal(uint64(0))) Expect(stats.GetReferenceCount()).To(Equal(uint64(1)), "the funding tx carried a reference") }) @@ -896,7 +895,7 @@ var _ = Describe("Restore", Ordered, func() { Eventually(func(g Gomega) { resp, err := joinerClient.GetAccount(staleCtx, &servicepb.GetAccountRequest{Ledger: ledgerName, Address: "join-fence"}) g.Expect(err).To(Succeed()) - g.Expect(resp.GetVolumes()["USD"].GetInput()).To(Equal("42")) + g.Expect(resp.FindVolume("USD", "").GetInput()).To(Equal("42")) }).Within(60*time.Second).ProbeEvery(500*time.Millisecond).Should(Succeed(), "learner never caught up on the post-restore raft log") @@ -905,15 +904,15 @@ var _ = Describe("Restore", Ordered, func() { // learner iff the restored store itself was transferred. aliceResp, err := joinerClient.GetAccount(staleCtx, &servicepb.GetAccountRequest{Ledger: ledgerName, Address: "alice"}) Expect(err).To(Succeed()) - Expect(aliceResp.GetVolumes()).To(HaveKey("USD"), + Expect(aliceResp.FindVolume("USD", "")).ToNot(BeNil(), "learner caught up by log replay alone: the restored state never reached it") - Expect(aliceResp.GetVolumes()["USD"].GetInput()).To(Equal("3000")) + Expect(aliceResp.FindVolume("USD", "").GetInput()).To(Equal("3000")) treasuryResp, err := joinerClient.GetAccount(staleCtx, &servicepb.GetAccountRequest{Ledger: ledger2, Address: "treasury"}) Expect(err).To(Succeed()) - Expect(treasuryResp.GetVolumes()).To(HaveKey("EUR"), + Expect(treasuryResp.FindVolume("EUR", "")).ToNot(BeNil(), "ledger untouched since the restore must still reach the learner") - Expect(treasuryResp.GetVolumes()["EUR"].GetInput()).To(Equal("50000")) + Expect(treasuryResp.FindVolume("EUR", "").GetInput()).To(Equal("50000")) }) }) }) From dceaa24f5adbc902ecaff915e022b36636f9443d Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 17 Jul 2026 10:57:45 +0200 Subject: [PATCH 35/35] test(EN-1334): handler + e2e coverage for GET numscripts/{name}/usage Closes the reviewer gap where only the pure mapper toTemplateUsageJSON was tested. Adds handler tests driving handleGetNumscriptUsage end-to-end via MockBackend + httptest (success, never-invoked zero, missing name/ledger 400, unknown ledger 404, backend error 500, no-leader 503, full-route integration), a pkg/actions.GetTemplateUsage helper, and an e2e spec that registers + invokes a template and polls the async usagebuilder counter (Eventually, no sleep), asserting the unknown-template-returns-zero vs unknown-ledger-404 contract. --- .../http/handlers_get_numscript_usage_test.go | 215 ++++++++++++++++++ pkg/actions/read.go | 11 + tests/e2e/business/numscript_usage_test.go | 102 +++++++++ 3 files changed, 328 insertions(+) create mode 100644 tests/e2e/business/numscript_usage_test.go diff --git a/internal/adapter/http/handlers_get_numscript_usage_test.go b/internal/adapter/http/handlers_get_numscript_usage_test.go index 2e6f9b65c6..7cc55f8a18 100644 --- a/internal/adapter/http/handlers_get_numscript_usage_test.go +++ b/internal/adapter/http/handlers_get_numscript_usage_test.go @@ -1,12 +1,21 @@ package http import ( + "context" "encoding/json" + "errors" + "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + logging "github.com/formancehq/go-libs/v5/pkg/observe/log" + + internalauth "github.com/formancehq/ledger/v3/internal/adapter/auth" + "github.com/formancehq/ledger/v3/internal/pkg/version" "github.com/formancehq/ledger/v3/internal/proto/commonpb" ) @@ -62,3 +71,209 @@ func TestToTemplateUsageJSON_MicrosecondUnitAndCamelCase(t *testing.T) { assert.JSONEq(t, `{"count":42,"lastUsed":"2023-11-14T22:13:20Z"}`, string(raw), "DTO must be camelCase with no raw protobuf field names or wire encoding") } + +// TestHandleGetNumscriptUsage_Success drives the handler end-to-end through the +// generated MockBackend: it asserts the writeOK envelope wraps the +// toTemplateUsageJSON payload (count + camelCase lastUsed), and that the handler +// forwards {ledgerName} and {name} to the backend unchanged. +func TestHandleGetNumscriptUsage_Success(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + require.Equal(t, "my-ledger", ledger) + require.Equal(t, "payout", name) + + return &commonpb.TemplateUsage{ + Count: 7, + LastUsed: &commonpb.Timestamp{Data: 1_700_000_000_000_000}, + }, nil + }).AnyTimes() + srv := newTestServer(t, backend) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/my-ledger/numscripts/payout/usage", nil, map[string]string{ + "ledgerName": "my-ledger", + "name": "payout", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusOK, w.Code) + + wrapper := decodeResponse[BaseResponse[templateUsageJSON]](t, w) + require.Equal(t, uint64(7), wrapper.Data.Count) + require.NotNil(t, wrapper.Data.LastUsed) + assert.Equal(t, "2023-11-14T22:13:20Z", *wrapper.Data.LastUsed) +} + +// TestHandleGetNumscriptUsage_NeverInvoked confirms the zero-valued contract: a +// never-invoked template on an existing ledger yields count 0 and an omitted +// lastUsed, not a 404 (the controller returns a zero TemplateUsage). +func TestHandleGetNumscriptUsage_NeverInvoked(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ string) (*commonpb.TemplateUsage, error) { + return &commonpb.TemplateUsage{}, nil + }).AnyTimes() + srv := newTestServer(t, backend) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/my-ledger/numscripts/unused/usage", nil, map[string]string{ + "ledgerName": "my-ledger", + "name": "unused", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusOK, w.Code) + + wrapper := decodeResponse[BaseResponse[templateUsageJSON]](t, w) + require.Equal(t, uint64(0), wrapper.Data.Count) + require.Nil(t, wrapper.Data.LastUsed, "never-invoked template must omit lastUsed") +} + +// TestHandleGetNumscriptUsage_MissingName asserts that an empty {name} URL param +// is rejected with 400 INVALID_REQUEST before the backend is ever consulted. +func TestHandleGetNumscriptUsage_MissingName(t *testing.T) { + t.Parallel() + + // No EXPECT() on the backend: gomock fails the test if GetTemplateUsage is + // called, proving the handler short-circuits on the missing name. + srv := newTestServer(t, NewMockBackend(gomock.NewController(t))) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/my-ledger/numscripts//usage", nil, map[string]string{ + "ledgerName": "my-ledger", + "name": "", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusBadRequest, w.Code) + + errResp := decodeResponse[ErrorResponse](t, w) + assert.Equal(t, "INVALID_REQUEST", errResp.ErrorCode) +} + +// TestHandleGetNumscriptUsage_MissingLedgerName asserts requireLedgerName gates +// an empty {ledgerName} with a 400 before touching the backend. +func TestHandleGetNumscriptUsage_MissingLedgerName(t *testing.T) { + t.Parallel() + + srv := newTestServer(t, NewMockBackend(gomock.NewController(t))) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/numscripts/payout/usage", nil, map[string]string{ + "ledgerName": "", + "name": "payout", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusBadRequest, w.Code) + + errResp := decodeResponse[ErrorResponse](t, w) + assert.Equal(t, "INVALID_REQUEST", errResp.ErrorCode) +} + +// TestHandleGetNumscriptUsage_LedgerNotFound confirms the handler routes a +// controller not-found through handleError into a 404. +func TestHandleGetNumscriptUsage_LedgerNotFound(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ string) (*commonpb.TemplateUsage, error) { + return nil, commonpb.NewNotFoundError("ledger %s not found", "missing") + }).AnyTimes() + srv := newTestServer(t, backend) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/missing/numscripts/payout/usage", nil, map[string]string{ + "ledgerName": "missing", + "name": "payout", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusNotFound, w.Code) +} + +// TestHandleGetNumscriptUsage_BackendError confirms a generic backend error is +// sanitized into a 500 via handleError's fallthrough. +func TestHandleGetNumscriptUsage_BackendError(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ string) (*commonpb.TemplateUsage, error) { + return nil, errors.New("usage store unavailable") + }).AnyTimes() + srv := newTestServer(t, backend) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/my-ledger/numscripts/payout/usage", nil, map[string]string{ + "ledgerName": "my-ledger", + "name": "payout", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusInternalServerError, w.Code) +} + +// TestHandleGetNumscriptUsage_NoLeaderError confirms a not-a-leader error maps +// to 503 (mirrors the sibling stats handler test). +func TestHandleGetNumscriptUsage_NoLeaderError(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, _, _ string) (*commonpb.TemplateUsage, error) { + return nil, commonpb.ErrNoLeader + }).AnyTimes() + srv := newTestServer(t, backend) + + w := httptest.NewRecorder() + r := newRequest(t, http.MethodGet, "/my-ledger/numscripts/payout/usage", nil, map[string]string{ + "ledgerName": "my-ledger", + "name": "payout", + }) + + srv.handleGetNumscriptUsage(w, r) + + require.Equal(t, http.StatusServiceUnavailable, w.Code) +} + +// TestHandleGetNumscriptUsage_FullRouteIntegration exercises the route through +// NewHandler + a real HTTP request, proving GET +// /v3/{ledgerName}/numscripts/{name}/usage is wired and the path params reach +// the handler. +func TestHandleGetNumscriptUsage_FullRouteIntegration(t *testing.T) { + t.Parallel() + + backend := NewMockBackend(gomock.NewController(t)) + backend.EXPECT().GetTemplateUsage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(_ context.Context, ledger, name string) (*commonpb.TemplateUsage, error) { + require.Equal(t, "my-ledger", ledger) + require.Equal(t, "payout", name) + + return &commonpb.TemplateUsage{Count: 3}, nil + }).AnyTimes() + + handler := NewHandler(logging.Testing(), backend, internalauth.AuthConfig{}, version.Info{}) + + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/v3/my-ledger/numscripts/payout/usage", nil) + + handler.ServeHTTP(w, r) + + require.Equal(t, http.StatusOK, w.Code) + + wrapper := decodeResponse[BaseResponse[templateUsageJSON]](t, w) + require.Equal(t, uint64(3), wrapper.Data.Count) +} diff --git a/pkg/actions/read.go b/pkg/actions/read.go index 4ce4f526a9..c2eae90c7d 100644 --- a/pkg/actions/read.go +++ b/pkg/actions/read.go @@ -313,6 +313,17 @@ func GetLedgerStats(ctx context.Context, client servicepb.BucketServiceClient, l }) } +// GetTemplateUsage retrieves the invocation counter and last-used timestamp +// for a Numscript template. The usagebuilder folds the counter asynchronously, +// so callers asserting on a freshly-invoked template must poll (Gomega +// Eventually / require.Eventually) rather than assume immediate visibility. +func GetTemplateUsage(ctx context.Context, client servicepb.BucketServiceClient, ledger, name string) (*commonpb.TemplateUsage, error) { + return client.GetTemplateUsage(ctx, &servicepb.GetTemplateUsageRequest{ + Ledger: ledger, + Name: name, + }) +} + // GetNumscript retrieves a numscript by name and optional version ("" = latest). func GetNumscript(ctx context.Context, client servicepb.BucketServiceClient, ledger, name, version string) (*commonpb.NumscriptInfo, error) { return client.GetNumscript(ctx, &servicepb.GetNumscriptRequest{ diff --git a/tests/e2e/business/numscript_usage_test.go b/tests/e2e/business/numscript_usage_test.go new file mode 100644 index 0000000000..7ab64a75f0 --- /dev/null +++ b/tests/e2e/business/numscript_usage_test.go @@ -0,0 +1,102 @@ +//go:build e2e + +package business + +import ( + "time" + + "github.com/formancehq/ledger/v3/pkg/actions" + + "github.com/formancehq/ledger/v3/internal/proto/servicepb" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var _ = Describe("GetTemplateUsage", Ordered, func() { + + Context("When a template is invoked via a script reference", Ordered, func() { + const ( + ledgerName = "template-usage-ledger" + templateName = "payout" + ) + + const payoutScript = ` +vars { + account $destination + monetary $amount +} + +send $amount ( + source = @world + destination = $destination +) +` + + BeforeAll(func() { + // Create the ledger and register the template in the numscript library. + _, err := sharedClient.Apply(sharedCtx, servicepb.UnsignedApplyRequest("", + actions.CreateLedgerAction(ledgerName, nil), + actions.SaveNumscriptWithVersionAction(ledgerName, templateName, payoutScript, "1.0.0"))) + Expect(err).To(Succeed()) + }) + + It("Should start at zero before any invocation", func() { + usage, err := actions.GetTemplateUsage(sharedCtx, sharedClient, ledgerName, templateName) + Expect(err).To(Succeed()) + Expect(usage.GetCount()).To(BeZero()) + Expect(usage.GetLastUsed()).To(BeNil(), "a never-invoked template has no lastUsed") + }) + + It("Should increment count and set lastUsed after a real invocation", func() { + // Invoke the template twice via script-reference transactions. + for i := 0; i < 2; i++ { + _, err := sharedClient.Apply(sharedCtx, servicepb.UnsignedApplyRequest("", + actions.CreateScriptRefTransactionAction(ledgerName, templateName, "1.0.0", map[string]string{ + "destination": "users:alice", + "amount": "USD/2 100", + }, nil))) + Expect(err).To(Succeed()) + } + + // The usagebuilder folds the counter asynchronously (one background + // tick behind the FSM), so poll rather than assume immediate + // visibility. Never time.Sleep. + Eventually(func(g Gomega) { + usage, err := actions.GetTemplateUsage(sharedCtx, sharedClient, ledgerName, templateName) + g.Expect(err).To(Succeed()) + g.Expect(usage.GetCount()).To(Equal(uint64(2)), "count must reflect both invocations") + g.Expect(usage.GetLastUsed()).NotTo(BeNil(), "lastUsed must be populated after invocation") + g.Expect(usage.GetLastUsed().GetData()).NotTo(BeZero(), "lastUsed timestamp must be a real value") + }).Within(30 * time.Second).WithPolling(200 * time.Millisecond).Should(Succeed()) + }) + }) + + Context("Unknown-template / unknown-ledger contract", func() { + const ledgerName = "template-usage-contract-ledger" + + BeforeAll(func() { + _, err := sharedClient.Apply(sharedCtx, servicepb.UnsignedApplyRequest("", actions.CreateLedgerAction(ledgerName, nil))) + Expect(err).To(Succeed()) + }) + + It("Should return zero (not NotFound) for an unknown template on an existing ledger", func() { + // The endpoint's contract is zero-valued, not 404: clients treat a + // never-seen template uniformly as count 0. Only an unknown ledger 404s. + usage, err := actions.GetTemplateUsage(sharedCtx, sharedClient, ledgerName, "never-registered") + Expect(err).To(Succeed()) + Expect(usage.GetCount()).To(BeZero()) + Expect(usage.GetLastUsed()).To(BeNil()) + }) + + It("Should return NotFound for an unknown ledger", func() { + _, err := actions.GetTemplateUsage(sharedCtx, sharedClient, "non-existent-ledger", "payout") + Expect(err).To(HaveOccurred()) + + st, ok := status.FromError(err) + Expect(ok).To(BeTrue()) + Expect(st.Code()).To(Equal(codes.NotFound)) + }) + }) +})