-
Notifications
You must be signed in to change notification settings - Fork 271
Expand file tree
/
Copy pathkvexecutor_test.go
More file actions
274 lines (235 loc) · 8.03 KB
/
Copy pathkvexecutor_test.go
File metadata and controls
274 lines (235 loc) · 8.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package executor
import (
"bytes"
"context"
"reflect"
"strings"
"testing"
"time"
)
func TestInitChain_Idempotency(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
genesisTime := time.Now()
initialHeight := uint64(1)
chainID := "test-chain"
// First call initializes genesis state
stateRoot1, err := exec.InitChain(ctx, genesisTime, initialHeight, chainID)
if err != nil {
t.Fatalf("InitChain failed on first call: %v", err)
}
// Second call should return the same genesis state root
stateRoot2, err := exec.InitChain(ctx, genesisTime, initialHeight, chainID)
if err != nil {
t.Fatalf("InitChain failed on second call: %v", err)
}
if !bytes.Equal(stateRoot1, stateRoot2) {
t.Errorf("Genesis state roots do not match: %s vs %s", stateRoot1, stateRoot2)
}
}
func TestGetTxs(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
// Inject transactions using the InjectTx method which sends to the channel
tx1 := []byte("a=1")
tx2 := []byte("b=2")
exec.InjectTx(tx1)
exec.InjectTx(tx2)
// Allow a brief moment for transactions to be processed by the channel if needed,
// though for buffered channels it should be immediate unless full.
time.Sleep(10 * time.Millisecond)
txs, err := exec.GetTxs(ctx)
if err != nil {
t.Fatalf("GetTxs returned error: %v", err)
}
if len(txs) != 2 {
t.Errorf("Expected 2 transactions, got %d", len(txs))
}
if !reflect.DeepEqual(txs[0], tx1) {
t.Errorf("Expected first tx 'a=1', got %s", string(txs[0]))
}
if !reflect.DeepEqual(txs[1], tx2) {
t.Errorf("Expected second tx 'b=2', got %s", string(txs[1]))
}
// GetTxs should drain the channel, so a second call should return empty or nil
txsAgain, err := exec.GetTxs(ctx)
if err != nil {
t.Fatalf("GetTxs (second call) returned error: %v", err)
}
if len(txsAgain) != 0 {
t.Errorf("Expected 0 transactions on second call (drained), got %d", len(txsAgain))
}
// Inject another transaction and verify it's available
tx3 := []byte("c=3")
exec.InjectTx(tx3)
time.Sleep(10 * time.Millisecond)
txsAfterReinject, err := exec.GetTxs(ctx)
if err != nil {
t.Fatalf("GetTxs returned error after re-inject: %v", err)
}
if len(txsAfterReinject) != 1 {
t.Errorf("Expected 1 transaction after re-inject, got %d", len(txsAfterReinject))
}
if !reflect.DeepEqual(txsAfterReinject[0], tx3) {
t.Errorf("Expected tx 'c=3' after re-inject, got %s", string(txsAfterReinject[0]))
}
}
func TestExecuteTxs_Valid(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
// Prepare valid transactions
txs := [][]byte{
[]byte("key1=value1"),
[]byte("key2=value2"),
}
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
if err != nil {
t.Fatalf("ExecuteTxs failed: %v", err)
}
// Check that stateRoot contains the updated key-value pairs
rootStr := string(result.UpdatedStateRoot)
if !strings.Contains(rootStr, "key1:value1;") || !strings.Contains(rootStr, "key2:value2;") {
t.Errorf("State root does not contain expected key-values: %s", rootStr)
}
}
func TestExecuteTxs_Invalid(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
// According to the Executor interface: "Must handle gracefully gibberish transactions"
// Invalid transactions should be filtered out, not cause errors
// Prepare invalid transactions (missing '=')
txs := [][]byte{
[]byte("invalidformat"),
[]byte("another_invalid_one"),
[]byte(""),
}
result, err := exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
if err != nil {
t.Fatalf("ExecuteTxs should handle gibberish gracefully, got error: %v", err)
}
// State root should still be computed (empty block is valid)
if result.UpdatedStateRoot == nil {
t.Error("Expected non-nil state root even with all invalid transactions")
}
// Test mix of valid and invalid transactions
mixedTxs := [][]byte{
[]byte("valid_key=valid_value"),
[]byte("invalidformat"),
[]byte("another_valid=value2"),
[]byte(""),
}
result2, err := exec.ExecuteTxs(ctx, mixedTxs, 2, time.Now(), result.UpdatedStateRoot)
if err != nil {
t.Fatalf("ExecuteTxs should filter invalid transactions and process valid ones, got error: %v", err)
}
// State root should contain only the valid transactions
rootStr := string(result2.UpdatedStateRoot)
if !strings.Contains(rootStr, "valid_key:valid_value") || !strings.Contains(rootStr, "another_valid:value2") {
t.Errorf("State root should contain valid transactions: %s", rootStr)
}
}
func TestSetFinal(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
// Test with valid blockHeight
err = exec.SetFinal(ctx, 1)
if err != nil {
t.Errorf("Expected nil error for valid blockHeight, got %v", err)
}
// Test with invalid blockHeight (zero)
err = exec.SetFinal(ctx, 0)
if err == nil {
t.Error("Expected error for blockHeight 0, got nil")
}
}
func TestReservedKeysExcludedFromAppHash(t *testing.T) {
exec, err := NewKVExecutor(t.TempDir(), "testdb")
if err != nil {
t.Fatalf("Failed to create KVExecutor: %v", err)
}
ctx := context.Background()
// Initialize chain to set up genesis state (this writes genesis reserved keys)
_, err = exec.InitChain(ctx, time.Now(), 1, "test-chain")
if err != nil {
t.Fatalf("Failed to initialize chain: %v", err)
}
// Add some application data
txs := [][]byte{
[]byte("user/key1=value1"),
[]byte("user/key2=value2"),
}
_, err = exec.ExecuteTxs(ctx, txs, 1, time.Now(), []byte(""))
if err != nil {
t.Fatalf("Failed to execute transactions: %v", err)
}
// Compute baseline state root
baselineStateRoot, err := exec.computeStateRoot(ctx)
if err != nil {
t.Fatalf("Failed to compute baseline state root: %v", err)
}
// Write to finalizedHeight (a reserved key)
err = exec.SetFinal(ctx, 5)
if err != nil {
t.Fatalf("Failed to set final height: %v", err)
}
// Verify finalizedHeight was written
finalizedHeightExists, err := exec.db.Has(ctx, finalizedHeightKey)
if err != nil {
t.Fatalf("Failed to check if finalizedHeight exists: %v", err)
}
if !finalizedHeightExists {
t.Error("Expected finalizedHeight to exist in database")
}
// State root should be unchanged (reserved keys excluded from calculation)
stateRootAfterReservedKeyWrite, err := exec.computeStateRoot(ctx)
if err != nil {
t.Fatalf("Failed to compute state root after writing reserved key: %v", err)
}
if string(baselineStateRoot) != string(stateRootAfterReservedKeyWrite) {
t.Errorf("State root changed after writing reserved key:\nBefore: %s\nAfter: %s",
string(baselineStateRoot), string(stateRootAfterReservedKeyWrite))
}
// Verify state root contains only user data, not reserved keys
stateRootStr := string(stateRootAfterReservedKeyWrite)
if !strings.Contains(stateRootStr, "user/key1:value1") ||
!strings.Contains(stateRootStr, "user/key2:value2") {
t.Errorf("State root should contain user data: %s", stateRootStr)
}
// Verify reserved keys are NOT in state root
for key := range reservedKeys {
keyStr := key.String()
if strings.Contains(stateRootStr, keyStr) {
t.Errorf("State root should NOT contain reserved key %s: %s", keyStr, stateRootStr)
}
}
// Verify that adding user data DOES change the state root
moreTxs := [][]byte{
[]byte("user/key3=value3"),
}
_, err = exec.ExecuteTxs(ctx, moreTxs, 2, time.Now(), stateRootAfterReservedKeyWrite)
if err != nil {
t.Fatalf("Failed to execute more transactions: %v", err)
}
finalStateRoot, err := exec.computeStateRoot(ctx)
if err != nil {
t.Fatalf("Failed to compute final state root: %v", err)
}
if string(baselineStateRoot) == string(finalStateRoot) {
t.Error("Expected state root to change after adding user data")
}
}