diff --git a/sei-tendermint/internal/autobahn/autobahn.proto b/sei-tendermint/internal/autobahn/autobahn.proto index 100cc2427a..b46b531fca 100644 --- a/sei-tendermint/internal/autobahn/autobahn.proto +++ b/sei-tendermint/internal/autobahn/autobahn.proto @@ -168,12 +168,6 @@ message PersistedInner { optional FullTimeoutVote timeout_vote = 6; } -// Wrapper for persisted data with sequence number. -message PersistedWrapper { - optional uint64 seq = 1; - optional bytes data = 2; -} - // Persisted availability prune anchor (AppQC + matching CommitQC pair). // Stored atomically in an A/B file; used as the crash-recovery pruning watermark. message PersistedAvailPruneAnchor { diff --git a/sei-tendermint/internal/autobahn/avail/state_test.go b/sei-tendermint/internal/autobahn/avail/state_test.go index 8562dc0f51..b9d4bf849b 100644 --- a/sei-tendermint/internal/autobahn/avail/state_test.go +++ b/sei-tendermint/internal/autobahn/avail/state_test.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "os" + "path/filepath" "testing" "time" - "google.golang.org/protobuf/proto" - "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/consensus/persist" "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/data" pb "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/pb" @@ -759,14 +759,15 @@ func TestNewStateWithPersistence(t *testing.T) { dir := t.TempDir() ds := data.NewState(&data.Config{Committee: committee}, utils.None[data.BlockStore]()) - // Write a valid PersistedWrapper whose Data payload is garbage. - // This simulates corruption at the application data level while - // keeping the outer A/B wrapper intact. - seq := uint64(1) - wrapper := &pb.PersistedWrapper{Seq: &seq, Data: []byte("not a valid protobuf")} - bz, err := proto.Marshal(wrapper) + // Create a throwaway persister to discover the A/B filenames, + // then corrupt them so NewState fails on load. + _, _, err := persist.NewPersister[*pb.PersistedAvailPruneAnchor](utils.Some(dir), innerFile) + require.NoError(t, err) + entries, err := os.ReadDir(dir) require.NoError(t, err) - require.NoError(t, persist.WriteRawFile(dir, innerFile, bz)) + for _, e := range entries { + require.NoError(t, os.WriteFile(filepath.Join(dir, e.Name()), []byte("corrupt"), 0600)) + } _, err = NewState(keys[0], ds, utils.Some(dir)) require.Error(t, err) diff --git a/sei-tendermint/internal/autobahn/consensus/persist/persist.go b/sei-tendermint/internal/autobahn/consensus/persist/persist.go index 7e2f6f614e..4a2dc6ca6e 100644 --- a/sei-tendermint/internal/autobahn/consensus/persist/persist.go +++ b/sei-tendermint/internal/autobahn/consensus/persist/persist.go @@ -34,14 +34,13 @@ package persist import ( + "encoding/binary" "errors" "fmt" + "hash/crc32" "os" "path/filepath" - "google.golang.org/protobuf/proto" - - "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/pb" "github.com/sei-protocol/sei-chain/sei-tendermint/internal/protoutils" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" ) @@ -52,11 +51,13 @@ const ( suffixB = "_b.pb" ) -// WriteRawFile writes raw bytes to the A file for a given prefix. -// Intended for tests that need to simulate corruption from outside the package. -func WriteRawFile(dir, prefix string, data []byte) error { - return os.WriteFile(filepath.Join(dir, prefix+suffixA), data, 0600) -} +var crc32c = crc32.MakeTable(crc32.Castagnoli) + +const ( + crcSize = 4 // CRC32-C prefix length + seqSize = 8 // uint64 little-endian + headerSize = crcSize + seqSize // file header: [4-byte CRC32-C BE][8-byte seq LE] +) // ErrNoData is returned by loadPersisted when no persisted files exist for the prefix. var ErrNoData = errors.New("no persisted data") @@ -67,6 +68,12 @@ var ErrNoData = errors.New("no persisted data") // I/O errors) are NOT wrapped with ErrCorrupt and cause loadPersisted to fail. var ErrCorrupt = errors.New("corrupt persisted data") +// dataWithSeq is the unit stored in each A/B file: a sequence number and a proto payload. +type dataWithSeq struct { + seq uint64 + data []byte // nil on fresh start +} + // Persister[T] is a strongly-typed persister for a proto message type. type Persister[T protoutils.Message] interface { Persist(T) error @@ -82,7 +89,7 @@ func newNoOpPersister[T protoutils.Message]() Persister[T] { } // abPersister writes data to A/B files with automatic seq management. -// Uses PersistedWrapper protobuf for crash-safe persistence. +// File format: [4-byte CRC32-C BE] [8-byte seq LE] [proto-marshalled message]. // Only created when config has a state dir; dir is always a valid path. // File selection is derived from seq: odd seq → A, even seq → B. type abPersister[T protoutils.Message] struct { @@ -121,13 +128,13 @@ func NewPersister[T protoutils.Message](dir utils.Option[string], prefix string) _ = probe.Close() _ = os.Remove(probe.Name()) - wrapper, err := loadPersisted(d, prefix) + ds, err := loadPersisted(d, prefix) if err != nil && !errors.Is(err, ErrNoData) { return nil, none, err } // Ensure both A/B files exist and are writable so Persist never creates new - // directory entries. Empty files are treated as non-existent by loadWrapped, + // directory entries. Empty files are treated as non-existent by loadFile, // so they won't interfere with loading on restart. for _, suffix := range []string{suffixA, suffixB} { path := filepath.Join(d, prefix+suffix) @@ -147,10 +154,9 @@ func NewPersister[T protoutils.Message](dir utils.Option[string], prefix string) _ = df.Close() } - // wrapper is nil on fresh start (ErrNoData); protobuf Get methods return zero values for nil. var loaded utils.Option[T] - if bz := wrapper.GetData(); bz != nil { - msg, err := protoutils.Unmarshal[T](bz) + if ds.data != nil { + msg, err := protoutils.Unmarshal[T](ds.data) if err != nil { return nil, none, fmt.Errorf("unmarshal persisted %s: %w", prefix, err) } @@ -159,13 +165,11 @@ func NewPersister[T protoutils.Message](dir utils.Option[string], prefix string) return &abPersister[T]{ dir: d, prefix: prefix, - seq: wrapper.GetSeq(), + seq: ds.seq, }, loaded, nil } -// Persist writes a proto message to persistent storage with seq wrapper. -// Not safe for concurrent use. -// Returns error on marshal or write failure. +// Persist writes a proto message to persistent storage. Not safe for concurrent use. func (w *abPersister[T]) Persist(msg T) error { data := protoutils.Marshal(msg) seq := w.seq + 1 @@ -175,61 +179,57 @@ func (w *abPersister[T]) Persist(msg T) error { if seq%2 == 1 { suffix = suffixA } - filename := w.prefix + suffix - wrapper := &pb.PersistedWrapper{ - Seq: &seq, - Data: data, - } - bz, err := proto.Marshal(wrapper) - if err != nil { - return fmt.Errorf("marshal wrapper: %w", err) - } - - if err := writeAndSync(filepath.Join(w.dir, filename), bz); err != nil { + filename := w.prefix + suffix + if err := writeFile(filepath.Join(w.dir, filename), dataWithSeq{seq: seq, data: data}); err != nil { return fmt.Errorf("persist to %s: %w", filename, err) } w.seq = seq return nil } -// loadWrapped loads a wrapped file, returning the PersistedWrapper proto. -// Returns os.ErrNotExist when the file does not exist (caller can use errors.Is). -// Returns other error on read or unmarshal failure. loadPersisted calls loadWrapped -// for both A and B and only fails when both fail; one corrupt file is tolerated. -// stateDir must be an existing directory (we do not create it). -func loadWrapped(stateDir, filename string) (*pb.PersistedWrapper, error) { +// loadFile reads a single A/B file and returns its contents as a dataWithSeq. +// Returns os.ErrNotExist when the file does not exist. +// Returns ErrCorrupt on CRC mismatch or truncated header. +// OS-level errors (permission denied, I/O) are returned unwrapped. +func loadFile(stateDir, filename string) (dataWithSeq, error) { path := filepath.Join(stateDir, filename) bz, err := os.ReadFile(path) //nolint:gosec // path is constructed from operator-configured stateDir + hardcoded filename suffix; no user-controlled input if errors.Is(err, os.ErrNotExist) { - return nil, os.ErrNotExist + return dataWithSeq{}, os.ErrNotExist } if err != nil { - // OS-level read error (permission denied, I/O error, etc.) — - // not wrapped with ErrCorrupt so loadPersisted propagates it. - return nil, fmt.Errorf("read %s: %w", filename, err) + return dataWithSeq{}, fmt.Errorf("read %s: %w", filename, err) } - // Treat empty files as non-existent. A valid wrapper must contain at least - // a seq number. Empty files are created by NewPersister to pre-populate - // directory entries so that Persist never needs to dir-sync. + // Empty files are created by NewPersister to pre-populate directory entries. if len(bz) == 0 { - return nil, os.ErrNotExist + return dataWithSeq{}, os.ErrNotExist + } + if len(bz) < headerSize { + return dataWithSeq{}, fmt.Errorf("%s: truncated (len %d < header %d): %w", filename, len(bz), headerSize, ErrCorrupt) } - var wrapper pb.PersistedWrapper - if err := proto.Unmarshal(bz, &wrapper); err != nil { - return nil, fmt.Errorf("unmarshal %s: %w", filename, fmt.Errorf("%v: %w", err, ErrCorrupt)) + + wantCRC := binary.BigEndian.Uint32(bz[:crcSize]) + payload := bz[crcSize:] + if got := crc32.Checksum(payload, crc32c); got != wantCRC { + return dataWithSeq{}, fmt.Errorf("%s: crc32 mismatch (got %08x, want %08x): %w", filename, got, wantCRC, ErrCorrupt) + } + + seq := binary.LittleEndian.Uint64(payload[:seqSize]) + if seq == 0 { + return dataWithSeq{}, fmt.Errorf("%s: zero seq: %w", filename, ErrCorrupt) } - return &wrapper, nil + return dataWithSeq{seq: seq, data: payload[seqSize:]}, nil } // loadPersisted loads persisted data for the given directory and prefix. // Tries both A and B files; if one is corrupt (e.g. crash during write), the other is used // so the validator can restart. Returns ErrNoData when no persisted files exist (use errors.Is). // Returns other error only when both files fail to load or state is inconsistent (same seq). -func loadPersisted(dir string, prefix string) (*pb.PersistedWrapper, error) { +func loadPersisted(dir string, prefix string) (dataWithSeq, error) { fileA, fileB := prefix+suffixA, prefix+suffixB - wrapperA, errA := loadWrapped(dir, fileA) - wrapperB, errB := loadWrapped(dir, fileB) + a, errA := loadFile(dir, fileA) + b, errB := loadFile(dir, fileB) // Fail fast on OS-level errors (permission denied, I/O errors). // Only ErrNotExist (fresh start) and ErrCorrupt (crash mid-write) are tolerable. @@ -247,45 +247,69 @@ func loadPersisted(dir string, prefix string) (*pb.PersistedWrapper, error) { logger.Warn("corrupt state file", "file", fe.file, "err", fe.err) continue } - return nil, fmt.Errorf("load %s: %w", fe.file, fe.err) + return dataWithSeq{}, fmt.Errorf("load %s: %w", fe.file, fe.err) } switch { case errA == nil && errB == nil: switch { - case wrapperA.GetSeq() > wrapperB.GetSeq(): - return wrapperA, nil - case wrapperB.GetSeq() > wrapperA.GetSeq(): - return wrapperB, nil + case a.seq > b.seq: + return a, nil + case b.seq > a.seq: + return b, nil default: - return nil, fmt.Errorf("corrupt state: both %s and %s have same seq; remove %s if acceptable", fileA, fileB, fileB) + return dataWithSeq{}, fmt.Errorf("corrupt state: both %s and %s have same seq; remove %s if acceptable", fileA, fileB, fileB) } case errA == nil: - return wrapperA, nil + return a, nil case errB == nil: - return wrapperB, nil + return b, nil default: if errors.Is(errA, os.ErrNotExist) && errors.Is(errB, os.ErrNotExist) { - return nil, ErrNoData + return dataWithSeq{}, ErrNoData } - return nil, fmt.Errorf("no valid state: %s: %v; %s: %v", fileA, errA, fileB, errB) + return dataWithSeq{}, fmt.Errorf("no valid state: %s: %v; %s: %v", fileA, errA, fileB, errB) } } -// writeAndSync writes data to a file path and fsyncs. No dir sync needed because -// NewPersister pre-creates both A/B files at startup. +// writeAndSync atomically replaces path contents with data (O_TRUNC) and fsyncs. +// Used by WAL persistence (blocks, commitqcs). func writeAndSync(path string, data []byte) error { f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) //nolint:gosec // path is stateDir + hardcoded suffix; not user-controlled if err != nil { return err } + defer func() { _ = f.Close() }() if _, err := f.Write(data); err != nil { - _ = f.Close() return err } - if err := f.Sync(); err != nil { - _ = f.Close() + return f.Sync() +} + +// writeFile writes an A/B state file: [4-byte CRC32-C BE][8-byte seq LE][proto data]. +// Encodes seq and computes CRC internally; writes chunks directly to avoid +// copying data into an intermediate buffer. The file is fsynced before return. +func writeFile(path string, d dataWithSeq) error { + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) //nolint:gosec // path is stateDir + hardcoded suffix; not user-controlled + if err != nil { return err } - return f.Close() + defer func() { _ = f.Close() }() + + var seqBuf [seqSize]byte + binary.LittleEndian.PutUint64(seqBuf[:], d.seq) + + // hash.Hash.Write never returns an error. + h := crc32.New(crc32c) + _, _ = h.Write(seqBuf[:]) + _, _ = h.Write(d.data) + + var crcBuf [crcSize]byte + binary.BigEndian.PutUint32(crcBuf[:], h.Sum32()) + for _, chunk := range [][]byte{crcBuf[:], seqBuf[:], d.data} { + if _, err := f.Write(chunk); err != nil { + return err + } + } + return f.Sync() } diff --git a/sei-tendermint/internal/autobahn/consensus/persist/persist_test.go b/sei-tendermint/internal/autobahn/consensus/persist/persist_test.go index 598ae7c6d7..c5f1d5ab71 100644 --- a/sei-tendermint/internal/autobahn/consensus/persist/persist_test.go +++ b/sei-tendermint/internal/autobahn/consensus/persist/persist_test.go @@ -1,33 +1,25 @@ package persist import ( + "encoding/binary" "errors" + "hash/crc32" "os" "path/filepath" "runtime" "testing" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/wrapperspb" - "github.com/sei-protocol/sei-chain/sei-tendermint/internal/autobahn/pb" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils/require" ) -// testMsg creates a PersistedWrapper with the given data payload, used as -// a convenient proto.Message for testing the generic Persister. -func testMsg(data string) *pb.PersistedWrapper { - return &pb.PersistedWrapper{Data: []byte(data)} -} - -func testMsgData(msg *pb.PersistedWrapper) string { - return string(msg.GetData()) -} - func TestPersisterAlternates(t *testing.T) { dir := t.TempDir() - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) // Both files should be pre-created (empty) by NewPersister for dir-sync optimization. @@ -37,39 +29,39 @@ func TestPersisterAlternates(t *testing.T) { require.NoError(t, errB, "B should be pre-created") // First write: goes to A (seq=1) - require.NoError(t, w.Persist(testMsg("data1"))) + require.NoError(t, w.Persist(wrapperspb.String("data1"))) // Second write: goes to B (seq=2) - require.NoError(t, w.Persist(testMsg("data2"))) + require.NoError(t, w.Persist(wrapperspb.String("data2"))) // Third write: goes to A (seq=3, overwrite) - require.NoError(t, w.Persist(testMsg("data3"))) + require.NoError(t, w.Persist(wrapperspb.String("data3"))) // loadPersisted should return data3 (highest seq) - wrapper, err := loadPersisted(dir, "test") + ds, err := loadPersisted(dir, "test") require.NoError(t, err) - var inner pb.PersistedWrapper - require.NoError(t, proto.Unmarshal(wrapper.GetData(), &inner)) - require.Equal(t, "data3", testMsgData(&inner)) + var inner wrapperspb.StringValue + require.NoError(t, proto.Unmarshal(ds.data, &inner)) + require.Equal(t, "data3", inner.GetValue()) } func TestPersisterPicksHigherSeq(t *testing.T) { dir := t.TempDir() - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) // Write three times: A(seq=1), B(seq=2), A(seq=3) - require.NoError(t, w.Persist(testMsg("first"))) - require.NoError(t, w.Persist(testMsg("second"))) - require.NoError(t, w.Persist(testMsg("third"))) + require.NoError(t, w.Persist(wrapperspb.String("first"))) + require.NoError(t, w.Persist(wrapperspb.String("second"))) + require.NoError(t, w.Persist(wrapperspb.String("third"))) // Should load "third" (seq=3 > seq=2) - w2, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w2, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "third", testMsgData(msg)) + require.Equal(t, "third", msg.GetValue()) _ = w2 } @@ -77,35 +69,35 @@ func TestLoadPersistedOneCorruptFileSucceeds(t *testing.T) { dir := t.TempDir() // Write to both files: A(seq=1), B(seq=2) - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w.Persist(testMsg("first"))) // seq=1, A - require.NoError(t, w.Persist(testMsg("second"))) // seq=2, B + require.NoError(t, w.Persist(wrapperspb.String("first"))) // seq=1, A + require.NoError(t, w.Persist(wrapperspb.String("second"))) // seq=2, B // Corrupt B (the winner) — should fall back to A err = os.WriteFile(filepath.Join(dir, "test"+suffixB), []byte("corrupt"), 0600) require.NoError(t, err) - _, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "first", testMsgData(msg)) + require.Equal(t, "first", msg.GetValue()) } func TestLoadPersistedBothCorruptError(t *testing.T) { dir := t.TempDir() // Write valid data to A only - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w.Persist(testMsg("valid"))) // seq=1, A + require.NoError(t, w.Persist(wrapperspb.String("valid"))) // seq=1, A // Corrupt A (B is empty, treated as non-existent) — both files fail err = os.WriteFile(filepath.Join(dir, "test"+suffixA), []byte("corrupt"), 0600) require.NoError(t, err) - _, _, err = NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, _, err = NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.Error(t, err) } @@ -113,29 +105,29 @@ func TestNewPersisterOneCorruptFileSucceeds(t *testing.T) { dir := t.TempDir() // Write to both files: A(seq=1), B(seq=2) - w1, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w1, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w1.Persist(testMsg("first"))) // seq=1, A - require.NoError(t, w1.Persist(testMsg("second"))) // seq=2, B + require.NoError(t, w1.Persist(wrapperspb.String("first"))) // seq=1, A + require.NoError(t, w1.Persist(wrapperspb.String("second"))) // seq=2, B // Corrupt B (the winner) — NewPersister should still succeed using A err = os.WriteFile(filepath.Join(dir, "test"+suffixB), []byte("corrupt"), 0600) require.NoError(t, err) - w2, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w2, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "first", testMsgData(msg)) + require.Equal(t, "first", msg.GetValue()) // A won (seq=1), so next write goes to B (the corrupt/loser slot) - require.NoError(t, w2.Persist(testMsg("recovered"))) // seq=2, B + require.NoError(t, w2.Persist(wrapperspb.String("recovered"))) // seq=2, B - _, loaded2, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, loaded2, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg2, ok := loaded2.Get() require.True(t, ok) - require.Equal(t, "recovered", testMsgData(msg2)) + require.Equal(t, "recovered", msg2.GetValue()) } func TestLoadPersistedEmptyDir(t *testing.T) { @@ -148,7 +140,7 @@ func TestLoadPersistedEmptyDir(t *testing.T) { func TestNewPersisterInvalidDirError(t *testing.T) { // State dir must already exist; invalid (nonexistent or not a directory) returns error - _, _, err := NewPersister[*pb.PersistedWrapper](utils.Some("/nonexistent/path/that/does/not/exist"), "test") + _, _, err := NewPersister[*wrapperspb.StringValue](utils.Some("/nonexistent/path/that/does/not/exist"), "test") require.Error(t, err) require.Contains(t, err.Error(), "invalid state dir") } @@ -160,13 +152,13 @@ func TestPersistWriteErrorReturnsError(t *testing.T) { t.Skip("chmod 000 on directory not reliable on Windows") } dir := t.TempDir() - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w.Persist(testMsg("data1"))) + require.NoError(t, w.Persist(wrapperspb.String("data1"))) // Remove all permissions from dir so OpenFile fails with EACCES require.NoError(t, os.Chmod(dir, 0000)) defer os.Chmod(dir, 0700) //nolint:errcheck - err = w.Persist(testMsg("data2")) + err = w.Persist(wrapperspb.String("data2")) require.Error(t, err) } @@ -178,29 +170,29 @@ func TestPersistWriteErrorDoesNotAdvanceSeq(t *testing.T) { t.Skip("chmod 000 on directory not reliable on Windows") } dir := t.TempDir() - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) // Successful write: seq=1 → A - require.NoError(t, w.Persist(testMsg("good"))) + require.NoError(t, w.Persist(wrapperspb.String("good"))) // Make dir unwritable so next Persist fails require.NoError(t, os.Chmod(dir, 0000)) - err = w.Persist(testMsg("fail")) + err = w.Persist(wrapperspb.String("fail")) require.Error(t, err) require.NoError(t, os.Chmod(dir, 0700)) // Next successful write should go to B (seq=2), preserving A. // If seq had incorrectly advanced during the failed write, this would // write to A (seq=3), overwriting our only good data. - require.NoError(t, w.Persist(testMsg("recovered"))) + require.NoError(t, w.Persist(wrapperspb.String("recovered"))) // Verify: "recovered" is latest. - _, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "recovered", testMsgData(msg)) + require.Equal(t, "recovered", msg.GetValue()) } func TestLoadPersistedOSErrorPropagates(t *testing.T) { @@ -213,10 +205,10 @@ func TestLoadPersistedOSErrorPropagates(t *testing.T) { dir := t.TempDir() // Write to both files: A(seq=1), B(seq=2) - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w.Persist(testMsg("first"))) // seq=1, A - require.NoError(t, w.Persist(testMsg("second"))) // seq=2, B + require.NoError(t, w.Persist(wrapperspb.String("first"))) // seq=1, A + require.NoError(t, w.Persist(wrapperspb.String("second"))) // seq=2, B // Make B unreadable (OS error, not corrupt data) pathB := filepath.Join(dir, "test"+suffixB) @@ -230,70 +222,140 @@ func TestLoadPersistedOSErrorPropagates(t *testing.T) { require.False(t, errors.Is(err, ErrNoData), "should not be ErrNoData") } -func TestLoadPersistedCorruptFallsBack(t *testing.T) { - // When one file is corrupt (unmarshal error), loadPersisted should - // fall back to the other file — this is the crash recovery path. - dir := t.TempDir() - - w, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") - require.NoError(t, err) - require.NoError(t, w.Persist(testMsg("first"))) // seq=1, A - require.NoError(t, w.Persist(testMsg("second"))) // seq=2, B - - // Corrupt B (simulates crash mid-write) - err = os.WriteFile(filepath.Join(dir, "test"+suffixB), []byte("garbage"), 0600) - require.NoError(t, err) - - // Should succeed using A, since B's error is ErrCorrupt (tolerable) - _, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") - require.NoError(t, err) - msg, ok := loaded.Get() - require.True(t, ok) - require.Equal(t, "first", testMsgData(msg)) -} - func TestNewPersisterResumeSeq(t *testing.T) { dir := t.TempDir() // Create persister and write some data - w1, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w1, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w1.Persist(testMsg("data1"))) // seq=1, A - require.NoError(t, w1.Persist(testMsg("data2"))) // seq=2, B - require.NoError(t, w1.Persist(testMsg("data3"))) // seq=3, A (winner) + require.NoError(t, w1.Persist(wrapperspb.String("data1"))) // seq=1, A + require.NoError(t, w1.Persist(wrapperspb.String("data2"))) // seq=2, B + require.NoError(t, w1.Persist(wrapperspb.String("data3"))) // seq=3, A (winner) // Create new persister (simulates restart) // A has seq=3 (winner), so new persister should write to B first to preserve A - w2, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w2, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w2.Persist(testMsg("data4"))) // seq=4, B (preserves A) + require.NoError(t, w2.Persist(wrapperspb.String("data4"))) // seq=4, B (preserves A) // Verify data4 is the latest (seq=4) - _, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "data4", testMsgData(msg)) + require.Equal(t, "data4", msg.GetValue()) } func TestNewPersisterPreservesWinner(t *testing.T) { dir := t.TempDir() // Write to both files: A=seq1, B=seq2 (B wins) - w1, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w1, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w1.Persist(testMsg("old"))) // seq=1, A - require.NoError(t, w1.Persist(testMsg("winner"))) // seq=2, B (winner) + require.NoError(t, w1.Persist(wrapperspb.String("old"))) // seq=1, A + require.NoError(t, w1.Persist(wrapperspb.String("winner"))) // seq=2, B (winner) // New persister should write to A first (preserve B) - w2, _, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + w2, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) - require.NoError(t, w2.Persist(testMsg("new"))) // seq=3, A (preserves B) + require.NoError(t, w2.Persist(wrapperspb.String("new"))) // seq=3, A (preserves B) // Verify "new" is the latest (seq=3) - _, loaded, err := NewPersister[*pb.PersistedWrapper](utils.Some(dir), "test") + _, loaded, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") require.NoError(t, err) msg, ok := loaded.Get() require.True(t, ok) - require.Equal(t, "new", testMsgData(msg)) + require.Equal(t, "new", msg.GetValue()) +} + +// --- CRC32 and file header validation tests --- + +func TestLoadFileDataCorruption(t *testing.T) { + dir := t.TempDir() + + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") + require.NoError(t, err) + require.NoError(t, w.Persist(wrapperspb.String("data"))) // seq=1, A + + // Flip a byte in the proto payload; CRC should catch it. + path := filepath.Join(dir, "test"+suffixA) + bz, err := os.ReadFile(path) + require.NoError(t, err) + bz[len(bz)-1] ^= 0xFF + require.NoError(t, os.WriteFile(path, bz, 0600)) + + _, err = loadFile(dir, "test"+suffixA) + require.Error(t, err) + require.True(t, errors.Is(err, ErrCorrupt)) + require.Contains(t, err.Error(), "crc32 mismatch") +} + +func TestLoadFileTruncatedHeader(t *testing.T) { + dir := t.TempDir() + + // A file shorter than the header is rejected. + require.NoError(t, os.WriteFile(filepath.Join(dir, "test"+suffixA), []byte("short"), 0600)) + + _, err := loadFile(dir, "test"+suffixA) + require.Error(t, err) + require.True(t, errors.Is(err, ErrCorrupt)) + require.Contains(t, err.Error(), "truncated") +} + +func TestLoadFileZeroSeq(t *testing.T) { + dir := t.TempDir() + + // Build a valid-CRC file with seq=0. + payload := make([]byte, seqSize) + binary.LittleEndian.PutUint64(payload, 0) + crc := crc32.Checksum(payload, crc32c) + buf := make([]byte, crcSize+len(payload)) + binary.BigEndian.PutUint32(buf[:crcSize], crc) + copy(buf[crcSize:], payload) + require.NoError(t, os.WriteFile(filepath.Join(dir, "test"+suffixA), buf, 0600)) + + _, err := loadFile(dir, "test"+suffixA) + require.Error(t, err) + require.True(t, errors.Is(err, ErrCorrupt)) + require.Contains(t, err.Error(), "zero seq") +} + +func TestLoadFileSeqCorruption(t *testing.T) { + dir := t.TempDir() + + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") + require.NoError(t, err) + require.NoError(t, w.Persist(wrapperspb.String("data"))) + + // Flip a bit in the seq portion of the file; CRC should catch it. + path := filepath.Join(dir, "test"+suffixA) + bz, err := os.ReadFile(path) + require.NoError(t, err) + bz[crcSize] ^= 0x01 // first byte of seq + require.NoError(t, os.WriteFile(path, bz, 0600)) + + _, err = loadFile(dir, "test"+suffixA) + require.Error(t, err) + require.True(t, errors.Is(err, ErrCorrupt)) + require.Contains(t, err.Error(), "crc32 mismatch") +} + +func TestPersistFileFormat(t *testing.T) { + dir := t.TempDir() + w, _, err := NewPersister[*wrapperspb.StringValue](utils.Some(dir), "test") + require.NoError(t, err) + require.NoError(t, w.Persist(wrapperspb.String("hello"))) + + bz, err := os.ReadFile(filepath.Join(dir, "test"+suffixA)) + require.NoError(t, err) + require.True(t, len(bz) >= headerSize) + + // Verify CRC covers the payload (seq + proto data). + wantCRC := crc32.Checksum(bz[crcSize:], crc32c) + gotCRC := binary.BigEndian.Uint32(bz[:crcSize]) + require.Equal(t, wantCRC, gotCRC) + + // Verify seq. + seq := binary.LittleEndian.Uint64(bz[crcSize : crcSize+seqSize]) + require.Equal(t, uint64(1), seq) } diff --git a/sei-tendermint/internal/autobahn/pb/autobahn.pb.go b/sei-tendermint/internal/autobahn/pb/autobahn.pb.go index 11715e751a..c80375fd42 100644 --- a/sei-tendermint/internal/autobahn/pb/autobahn.pb.go +++ b/sei-tendermint/internal/autobahn/pb/autobahn.pb.go @@ -1384,59 +1384,6 @@ func (x *PersistedInner) GetTimeoutVote() *FullTimeoutVote { return nil } -// Wrapper for persisted data with sequence number. -type PersistedWrapper struct { - state protoimpl.MessageState `protogen:"open.v1"` - Seq *uint64 `protobuf:"varint,1,opt,name=seq,proto3,oneof" json:"seq,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof" json:"data,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PersistedWrapper) Reset() { - *x = PersistedWrapper{} - mi := &file_autobahn_autobahn_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PersistedWrapper) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PersistedWrapper) ProtoMessage() {} - -func (x *PersistedWrapper) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[23] - 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 PersistedWrapper.ProtoReflect.Descriptor instead. -func (*PersistedWrapper) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{23} -} - -func (x *PersistedWrapper) GetSeq() uint64 { - if x != nil && x.Seq != nil { - return *x.Seq - } - return 0 -} - -func (x *PersistedWrapper) GetData() []byte { - if x != nil { - return x.Data - } - return nil -} - // Persisted availability prune anchor (AppQC + matching CommitQC pair). // Stored atomically in an A/B file; used as the crash-recovery pruning watermark. type PersistedAvailPruneAnchor struct { @@ -1449,7 +1396,7 @@ type PersistedAvailPruneAnchor struct { func (x *PersistedAvailPruneAnchor) Reset() { *x = PersistedAvailPruneAnchor{} - mi := &file_autobahn_autobahn_proto_msgTypes[24] + mi := &file_autobahn_autobahn_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1461,7 +1408,7 @@ func (x *PersistedAvailPruneAnchor) String() string { func (*PersistedAvailPruneAnchor) ProtoMessage() {} func (x *PersistedAvailPruneAnchor) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[24] + mi := &file_autobahn_autobahn_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1474,7 +1421,7 @@ func (x *PersistedAvailPruneAnchor) ProtoReflect() protoreflect.Message { // Deprecated: Use PersistedAvailPruneAnchor.ProtoReflect.Descriptor instead. func (*PersistedAvailPruneAnchor) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{24} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{23} } func (x *PersistedAvailPruneAnchor) GetAppQc() *AppQC { @@ -1501,7 +1448,7 @@ type AppQC struct { func (x *AppQC) Reset() { *x = AppQC{} - mi := &file_autobahn_autobahn_proto_msgTypes[25] + mi := &file_autobahn_autobahn_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1513,7 +1460,7 @@ func (x *AppQC) String() string { func (*AppQC) ProtoMessage() {} func (x *AppQC) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[25] + mi := &file_autobahn_autobahn_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1526,7 +1473,7 @@ func (x *AppQC) ProtoReflect() protoreflect.Message { // Deprecated: Use AppQC.ProtoReflect.Descriptor instead. func (*AppQC) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{25} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{24} } func (x *AppQC) GetVote() *AppProposal { @@ -1557,7 +1504,7 @@ type AppProposal struct { func (x *AppProposal) Reset() { *x = AppProposal{} - mi := &file_autobahn_autobahn_proto_msgTypes[26] + mi := &file_autobahn_autobahn_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1569,7 +1516,7 @@ func (x *AppProposal) String() string { func (*AppProposal) ProtoMessage() {} func (x *AppProposal) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[26] + mi := &file_autobahn_autobahn_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1582,7 +1529,7 @@ func (x *AppProposal) ProtoReflect() protoreflect.Message { // Deprecated: Use AppProposal.ProtoReflect.Descriptor instead. func (*AppProposal) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{26} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{25} } func (x *AppProposal) GetGlobalNumber() uint64 { @@ -1626,7 +1573,7 @@ type Msg struct { func (x *Msg) Reset() { *x = Msg{} - mi := &file_autobahn_autobahn_proto_msgTypes[27] + mi := &file_autobahn_autobahn_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1638,7 +1585,7 @@ func (x *Msg) String() string { func (*Msg) ProtoMessage() {} func (x *Msg) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[27] + mi := &file_autobahn_autobahn_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1651,7 +1598,7 @@ func (x *Msg) ProtoReflect() protoreflect.Message { // Deprecated: Use Msg.ProtoReflect.Descriptor instead. func (*Msg) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{27} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{26} } func (x *Msg) GetT() isMsg_T { @@ -1783,7 +1730,7 @@ type SignedMsg struct { func (x *SignedMsg) Reset() { *x = SignedMsg{} - mi := &file_autobahn_autobahn_proto_msgTypes[28] + mi := &file_autobahn_autobahn_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1795,7 +1742,7 @@ func (x *SignedMsg) String() string { func (*SignedMsg) ProtoMessage() {} func (x *SignedMsg) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[28] + mi := &file_autobahn_autobahn_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1808,7 +1755,7 @@ func (x *SignedMsg) ProtoReflect() protoreflect.Message { // Deprecated: Use SignedMsg.ProtoReflect.Descriptor instead. func (*SignedMsg) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{28} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{27} } func (x *SignedMsg) GetMsg() *Msg { @@ -1842,7 +1789,7 @@ type ConsensusReq struct { func (x *ConsensusReq) Reset() { *x = ConsensusReq{} - mi := &file_autobahn_autobahn_proto_msgTypes[29] + mi := &file_autobahn_autobahn_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1854,7 +1801,7 @@ func (x *ConsensusReq) String() string { func (*ConsensusReq) ProtoMessage() {} func (x *ConsensusReq) ProtoReflect() protoreflect.Message { - mi := &file_autobahn_autobahn_proto_msgTypes[29] + mi := &file_autobahn_autobahn_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1867,7 +1814,7 @@ func (x *ConsensusReq) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsensusReq.ProtoReflect.Descriptor instead. func (*ConsensusReq) Descriptor() ([]byte, []int) { - return file_autobahn_autobahn_proto_rawDescGZIP(), []int{29} + return file_autobahn_autobahn_proto_rawDescGZIP(), []int{28} } func (x *ConsensusReq) GetT() isConsensusReq_T { @@ -2113,12 +2060,7 @@ const file_autobahn_autobahn_proto_rawDesc = "" + "\v_timeout_qcB\x0e\n" + "\f_commit_voteB\x0f\n" + "\r_prepare_voteB\x0f\n" + - "\r_timeout_vote\"S\n" + - "\x10PersistedWrapper\x12\x15\n" + - "\x03seq\x18\x01 \x01(\x04H\x00R\x03seq\x88\x01\x01\x12\x17\n" + - "\x04data\x18\x02 \x01(\fH\x01R\x04data\x88\x01\x01B\x06\n" + - "\x04_seqB\a\n" + - "\x05_data\"\x97\x01\n" + + "\r_timeout_vote\"\x97\x01\n" + "\x19PersistedAvailPruneAnchor\x12+\n" + "\x06app_qc\x18\x01 \x01(\v2\x0f.autobahn.AppQCH\x00R\x05appQc\x88\x01\x01\x124\n" + "\tcommit_qc\x18\x02 \x01(\v2\x12.autobahn.CommitQCH\x01R\bcommitQc\x88\x01\x01B\t\n" + @@ -2171,7 +2113,7 @@ func file_autobahn_autobahn_proto_rawDescGZIP() []byte { return file_autobahn_autobahn_proto_rawDescData } -var file_autobahn_autobahn_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_autobahn_autobahn_proto_msgTypes = make([]protoimpl.MessageInfo, 30) var file_autobahn_autobahn_proto_goTypes = []any{ (*Timestamp)(nil), // 0: autobahn.Timestamp (*Duration)(nil), // 1: autobahn.Duration @@ -2196,17 +2138,16 @@ var file_autobahn_autobahn_proto_goTypes = []any{ (*TimeoutQC)(nil), // 20: autobahn.TimeoutQC (*FullTimeoutVote)(nil), // 21: autobahn.FullTimeoutVote (*PersistedInner)(nil), // 22: autobahn.PersistedInner - (*PersistedWrapper)(nil), // 23: autobahn.PersistedWrapper - (*PersistedAvailPruneAnchor)(nil), // 24: autobahn.PersistedAvailPruneAnchor - (*AppQC)(nil), // 25: autobahn.AppQC - (*AppProposal)(nil), // 26: autobahn.AppProposal - (*Msg)(nil), // 27: autobahn.Msg - (*SignedMsg)(nil), // 28: autobahn.SignedMsg - (*ConsensusReq)(nil), // 29: autobahn.ConsensusReq - nil, // 30: autobahn.TransactionHeader.PropertiesEntry + (*PersistedAvailPruneAnchor)(nil), // 23: autobahn.PersistedAvailPruneAnchor + (*AppQC)(nil), // 24: autobahn.AppQC + (*AppProposal)(nil), // 25: autobahn.AppProposal + (*Msg)(nil), // 26: autobahn.Msg + (*SignedMsg)(nil), // 27: autobahn.SignedMsg + (*ConsensusReq)(nil), // 28: autobahn.ConsensusReq + nil, // 29: autobahn.TransactionHeader.PropertiesEntry } var file_autobahn_autobahn_proto_depIdxs = []int32{ - 30, // 0: autobahn.TransactionHeader.properties:type_name -> autobahn.TransactionHeader.PropertiesEntry + 29, // 0: autobahn.TransactionHeader.properties:type_name -> autobahn.TransactionHeader.PropertiesEntry 2, // 1: autobahn.TransactionHeader.timestamps:type_name -> autobahn.TransactionTimestamps 3, // 2: autobahn.Transaction.header:type_name -> autobahn.TransactionHeader 6, // 3: autobahn.Signature.key:type_name -> autobahn.PublicKey @@ -2220,10 +2161,10 @@ var file_autobahn_autobahn_proto_depIdxs = []int32{ 13, // 11: autobahn.Proposal.view:type_name -> autobahn.View 0, // 12: autobahn.Proposal.created_at:type_name -> autobahn.Timestamp 12, // 13: autobahn.Proposal.lane_ranges:type_name -> autobahn.LaneRange - 26, // 14: autobahn.Proposal.app:type_name -> autobahn.AppProposal - 28, // 15: autobahn.FullProposal.proposal:type_name -> autobahn.SignedMsg + 25, // 14: autobahn.Proposal.app:type_name -> autobahn.AppProposal + 27, // 15: autobahn.FullProposal.proposal:type_name -> autobahn.SignedMsg 11, // 16: autobahn.FullProposal.lane_qcs:type_name -> autobahn.LaneQC - 25, // 17: autobahn.FullProposal.app_qc:type_name -> autobahn.AppQC + 24, // 17: autobahn.FullProposal.app_qc:type_name -> autobahn.AppQC 20, // 18: autobahn.FullProposal.timeout_qc:type_name -> autobahn.TimeoutQC 14, // 19: autobahn.PrepareQC.vote:type_name -> autobahn.Proposal 7, // 20: autobahn.PrepareQC.sigs:type_name -> autobahn.Signature @@ -2232,19 +2173,19 @@ var file_autobahn_autobahn_proto_depIdxs = []int32{ 17, // 23: autobahn.FullCommitQC.qc:type_name -> autobahn.CommitQC 8, // 24: autobahn.FullCommitQC.headers:type_name -> autobahn.BlockHeader 13, // 25: autobahn.TimeoutVote.view:type_name -> autobahn.View - 28, // 26: autobahn.TimeoutQC.votes:type_name -> autobahn.SignedMsg + 27, // 26: autobahn.TimeoutQC.votes:type_name -> autobahn.SignedMsg 16, // 27: autobahn.TimeoutQC.latest_prepare_qc:type_name -> autobahn.PrepareQC - 28, // 28: autobahn.FullTimeoutVote.vote:type_name -> autobahn.SignedMsg + 27, // 28: autobahn.FullTimeoutVote.vote:type_name -> autobahn.SignedMsg 16, // 29: autobahn.FullTimeoutVote.latest_prepare_qc:type_name -> autobahn.PrepareQC 17, // 30: autobahn.PersistedInner.commit_qc:type_name -> autobahn.CommitQC 16, // 31: autobahn.PersistedInner.prepare_qc:type_name -> autobahn.PrepareQC 20, // 32: autobahn.PersistedInner.timeout_qc:type_name -> autobahn.TimeoutQC - 28, // 33: autobahn.PersistedInner.commit_vote:type_name -> autobahn.SignedMsg - 28, // 34: autobahn.PersistedInner.prepare_vote:type_name -> autobahn.SignedMsg + 27, // 33: autobahn.PersistedInner.commit_vote:type_name -> autobahn.SignedMsg + 27, // 34: autobahn.PersistedInner.prepare_vote:type_name -> autobahn.SignedMsg 21, // 35: autobahn.PersistedInner.timeout_vote:type_name -> autobahn.FullTimeoutVote - 25, // 36: autobahn.PersistedAvailPruneAnchor.app_qc:type_name -> autobahn.AppQC + 24, // 36: autobahn.PersistedAvailPruneAnchor.app_qc:type_name -> autobahn.AppQC 17, // 37: autobahn.PersistedAvailPruneAnchor.commit_qc:type_name -> autobahn.CommitQC - 26, // 38: autobahn.AppQC.vote:type_name -> autobahn.AppProposal + 25, // 38: autobahn.AppQC.vote:type_name -> autobahn.AppProposal 7, // 39: autobahn.AppQC.sigs:type_name -> autobahn.Signature 10, // 40: autobahn.Msg.lane_proposal:type_name -> autobahn.Block 8, // 41: autobahn.Msg.lane_vote:type_name -> autobahn.BlockHeader @@ -2252,12 +2193,12 @@ var file_autobahn_autobahn_proto_depIdxs = []int32{ 14, // 43: autobahn.Msg.prepare_vote:type_name -> autobahn.Proposal 14, // 44: autobahn.Msg.commit_vote:type_name -> autobahn.Proposal 19, // 45: autobahn.Msg.timeout_vote:type_name -> autobahn.TimeoutVote - 26, // 46: autobahn.Msg.app_vote:type_name -> autobahn.AppProposal - 27, // 47: autobahn.SignedMsg.msg:type_name -> autobahn.Msg + 25, // 46: autobahn.Msg.app_vote:type_name -> autobahn.AppProposal + 26, // 47: autobahn.SignedMsg.msg:type_name -> autobahn.Msg 7, // 48: autobahn.SignedMsg.sig:type_name -> autobahn.Signature 15, // 49: autobahn.ConsensusReq.proposal:type_name -> autobahn.FullProposal - 28, // 50: autobahn.ConsensusReq.prepare_vote:type_name -> autobahn.SignedMsg - 28, // 51: autobahn.ConsensusReq.commit_vote:type_name -> autobahn.SignedMsg + 27, // 50: autobahn.ConsensusReq.prepare_vote:type_name -> autobahn.SignedMsg + 27, // 51: autobahn.ConsensusReq.commit_vote:type_name -> autobahn.SignedMsg 21, // 52: autobahn.ConsensusReq.timeout_vote:type_name -> autobahn.FullTimeoutVote 20, // 53: autobahn.ConsensusReq.timeout_qc:type_name -> autobahn.TimeoutQC 54, // [54:54] is the sub-list for method output_type @@ -2288,9 +2229,8 @@ func file_autobahn_autobahn_proto_init() { file_autobahn_autobahn_proto_msgTypes[21].OneofWrappers = []any{} file_autobahn_autobahn_proto_msgTypes[22].OneofWrappers = []any{} file_autobahn_autobahn_proto_msgTypes[23].OneofWrappers = []any{} - file_autobahn_autobahn_proto_msgTypes[24].OneofWrappers = []any{} - file_autobahn_autobahn_proto_msgTypes[26].OneofWrappers = []any{} - file_autobahn_autobahn_proto_msgTypes[27].OneofWrappers = []any{ + file_autobahn_autobahn_proto_msgTypes[25].OneofWrappers = []any{} + file_autobahn_autobahn_proto_msgTypes[26].OneofWrappers = []any{ (*Msg_LaneProposal)(nil), (*Msg_LaneVote)(nil), (*Msg_Proposal)(nil), @@ -2299,7 +2239,7 @@ func file_autobahn_autobahn_proto_init() { (*Msg_TimeoutVote)(nil), (*Msg_AppVote)(nil), } - file_autobahn_autobahn_proto_msgTypes[29].OneofWrappers = []any{ + file_autobahn_autobahn_proto_msgTypes[28].OneofWrappers = []any{ (*ConsensusReq_Proposal)(nil), (*ConsensusReq_PrepareVote)(nil), (*ConsensusReq_CommitVote)(nil), @@ -2312,7 +2252,7 @@ func file_autobahn_autobahn_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_autobahn_autobahn_proto_rawDesc), len(file_autobahn_autobahn_proto_rawDesc)), NumEnums: 0, - NumMessages: 31, + NumMessages: 30, NumExtensions: 0, NumServices: 0, },