Skip to content

Commit 744c8e5

Browse files
committed
test: cover pending.go helpers and legacy migration
1 parent 3efcf93 commit 744c8e5

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package executing
2+
3+
import (
4+
"crypto/sha256"
5+
"testing"
6+
"time"
7+
8+
ds "github.com/ipfs/go-datastore"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/evstack/ev-node/block/internal/common"
13+
"github.com/evstack/ev-node/types"
14+
)
15+
16+
// buildHeaderAndData constructs a SignedHeader and Data at the given height.
17+
// An empty sig yields the legacy pre-upgrade pending marker; a non-empty sig
18+
// yields a signed block, which migration must refuse to treat as pending.
19+
func buildHeaderAndData(
20+
t *testing.T,
21+
fx executorTestFixture,
22+
height uint64,
23+
sig types.Signature,
24+
) (*types.SignedHeader, *types.Data) {
25+
t.Helper()
26+
27+
state := fx.Exec.getLastState()
28+
gen := fx.Exec.genesis
29+
30+
pubKey, err := fx.Exec.signer.GetPublic()
31+
require.NoError(t, err)
32+
validatorHash, err := common.DefaultBlockOptions().ValidatorHasherProvider(gen.ProposerAddress, pubKey)
33+
require.NoError(t, err)
34+
35+
header := &types.SignedHeader{
36+
Header: types.Header{
37+
Version: types.Version{
38+
Block: state.Version.Block,
39+
App: state.Version.App,
40+
},
41+
BaseHeader: types.BaseHeader{
42+
ChainID: gen.ChainID,
43+
Height: height,
44+
Time: uint64(time.Now().UnixNano()),
45+
},
46+
AppHash: state.AppHash,
47+
ProposerAddress: gen.ProposerAddress,
48+
ValidatorHash: validatorHash,
49+
},
50+
Signature: sig,
51+
Signer: types.Signer{
52+
PubKey: pubKey,
53+
Address: gen.ProposerAddress,
54+
},
55+
}
56+
57+
data := &types.Data{
58+
Txs: []types.Tx{[]byte("legacy_tx")},
59+
Metadata: &types.Metadata{
60+
ChainID: header.ChainID(),
61+
Height: header.Height(),
62+
Time: header.BaseHeader.Time,
63+
},
64+
}
65+
header.DataHash = data.DACommitment()
66+
67+
return header, data
68+
}
69+
70+
// saveLegacyBlock builds a block and stores it at the given height in the
71+
// pre-upgrade format (unsigned = pending, signed = reject on migration).
72+
func saveLegacyBlock(t *testing.T, fx executorTestFixture, height uint64, sig types.Signature) (*types.SignedHeader, *types.Data) {
73+
t.Helper()
74+
h, d := buildHeaderAndData(t, fx, height, sig)
75+
batch, err := fx.MemStore.NewBatch(t.Context())
76+
require.NoError(t, err)
77+
require.NoError(t, batch.SaveBlockData(h, d, &sig))
78+
require.NoError(t, batch.Commit())
79+
return h, d
80+
}
81+
82+
func TestMigrateLegacyPendingBlock(t *testing.T) {
83+
t.Run("migrates unsigned legacy pending block", func(t *testing.T) {
84+
fx := setupTestExecutor(t, 1000)
85+
defer fx.Cancel()
86+
87+
// Empty signature indicates legacy pending block
88+
header, data := saveLegacyBlock(t, fx, 1, types.Signature{})
89+
90+
_, _, err := fx.MemStore.GetBlockData(t.Context(), 1)
91+
require.NoError(t, err, "precondition: legacy block should exist before migration")
92+
93+
require.NoError(t, fx.Exec.migrateLegacyPendingBlock(fx.Exec.ctx))
94+
95+
gotHeader, gotData, err := fx.Exec.getPendingBlock(fx.Exec.ctx)
96+
require.NoError(t, err)
97+
require.NotNil(t, gotHeader)
98+
require.NotNil(t, gotData)
99+
assert.Equal(t, uint64(1), gotHeader.Height())
100+
assert.Equal(t, data.DACommitment(), gotHeader.DataHash)
101+
assert.Equal(t, len(data.Txs), len(gotData.Txs))
102+
103+
_, _, err = fx.MemStore.GetBlockData(t.Context(), 1)
104+
assert.Error(t, err, "legacy header/data keys should be deleted after migration")
105+
106+
_, err = fx.MemStore.GetSignature(t.Context(), 1)
107+
assert.Error(t, err, "legacy signature key should be deleted after migration")
108+
109+
headerBlob, err := header.MarshalBinary()
110+
require.NoError(t, err)
111+
hash := sha256.Sum256(headerBlob)
112+
_, _, err = fx.MemStore.GetBlockByHash(t.Context(), hash[:])
113+
assert.Error(t, err, "legacy index key should be deleted after migration")
114+
})
115+
116+
t.Run("no-op when no candidate block exists", func(t *testing.T) {
117+
fx := setupTestExecutor(t, 1000)
118+
defer fx.Cancel()
119+
120+
// Ensure migrateLegacyPendingBlock does not error when no legacy pending block exists.
121+
require.NoError(t, fx.Exec.migrateLegacyPendingBlock(fx.Exec.ctx))
122+
123+
// Assert pending block is empty.
124+
header, data, err := fx.Exec.getPendingBlock(fx.Exec.ctx)
125+
require.NoError(t, err)
126+
assert.Nil(t, header)
127+
assert.Nil(t, data)
128+
})
129+
130+
t.Run("errors when candidate block has signature", func(t *testing.T) {
131+
fx := setupTestExecutor(t, 1000)
132+
defer fx.Cancel()
133+
134+
// Non-empty signature indicates a signed block, not a legacy pending block (indicates some kind of corruption).
135+
sig := types.Signature([]byte("not-empty"))
136+
saveLegacyBlock(t, fx, 1, sig)
137+
138+
// migrateLegacyPendingBlock should error when it finds a legacy block with a signature,
139+
// rather than silently migrate a signed block and risk losing/duplicating it.
140+
err := fx.Exec.migrateLegacyPendingBlock(fx.Exec.ctx)
141+
require.Error(t, err)
142+
assert.Contains(t, err.Error(), "pending block with signatures found")
143+
144+
// Assert block was not migrated (i.e. still exists at legacy location and not at pending keys).
145+
_, _, err = fx.MemStore.GetBlockData(t.Context(), 1)
146+
assert.NoError(t, err)
147+
148+
// Assert pending keys are still empty.
149+
_, err = fx.MemStore.GetMetadata(t.Context(), headerKey)
150+
assert.ErrorIs(t, err, ds.ErrNotFound)
151+
_, err = fx.MemStore.GetMetadata(t.Context(), dataKey)
152+
assert.ErrorIs(t, err, ds.ErrNotFound)
153+
})
154+
}
155+
156+
func TestGetPendingBlock_CorruptState(t *testing.T) {
157+
fx := setupTestExecutor(t, 1000)
158+
defer fx.Cancel()
159+
160+
// Corrupt the pending block state by writing a header key without corresponding data.
161+
require.NoError(t, fx.MemStore.SetMetadata(t.Context(), headerKey, []byte("whatever")))
162+
163+
// Should error as only a valid header/data pair or empty state are expected.
164+
_, _, err := fx.Exec.getPendingBlock(fx.Exec.ctx)
165+
require.Error(t, err)
166+
assert.Contains(t, err.Error(), "corrupt state")
167+
}
168+
169+
func TestGetPendingBlock_Empty(t *testing.T) {
170+
fx := setupTestExecutor(t, 1000)
171+
defer fx.Cancel()
172+
173+
// Empty state should return nil header/data without error.
174+
h, d, err := fx.Exec.getPendingBlock(fx.Exec.ctx)
175+
require.NoError(t, err)
176+
assert.Nil(t, h)
177+
assert.Nil(t, d)
178+
}
179+
180+
func TestDeletePendingBlock(t *testing.T) {
181+
fx := setupTestExecutor(t, 1000)
182+
defer fx.Cancel()
183+
184+
// Save a pending block directly to the store.
185+
header, data := buildHeaderAndData(t, fx, 1, types.Signature{})
186+
require.NoError(t, fx.Exec.savePendingBlock(fx.Exec.ctx, header, data))
187+
188+
// Assert block is retrievable before deletion.
189+
gotHeader, gotData, err := fx.Exec.getPendingBlock(fx.Exec.ctx)
190+
require.NoError(t, err, "precondition: pending block should be readable")
191+
require.NotNil(t, gotHeader, "precondition: stored header should not be nil")
192+
require.NotNil(t, gotData, "precondition: stored data should not be nil")
193+
assert.Equal(t, header.Height(), gotHeader.Height(), "stored header height mismatch")
194+
assert.Equal(t, header.ChainID(), gotHeader.ChainID(), "stored header chain id mismatch")
195+
assert.Equal(t, header.DataHash, gotHeader.DataHash, "stored header data hash mismatch")
196+
assert.Equal(t, len(data.Txs), len(gotData.Txs), "stored data tx count mismatch")
197+
assert.Equal(t, data.DACommitment(), gotData.DACommitment(), "stored data DA commitment mismatch")
198+
199+
// Delete the pending block.
200+
require.NoError(t, fx.Exec.deletePendingBlock(fx.Exec.ctx))
201+
202+
// Assert pending block is no longer retrievable.
203+
_, err = fx.MemStore.GetMetadata(t.Context(), headerKey)
204+
assert.ErrorIs(t, err, ds.ErrNotFound)
205+
_, err = fx.MemStore.GetMetadata(t.Context(), dataKey)
206+
assert.ErrorIs(t, err, ds.ErrNotFound)
207+
}

0 commit comments

Comments
 (0)