From 1bedeabb0ba610916c355497943a643bfbcd07f3 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 4 May 2023 16:37:38 -0400 Subject: [PATCH 01/10] wip Signed-off-by: Sam Batschelet --- tstate/tstate_test.go | 577 +++++++++++++++++++++++------------------- 1 file changed, 314 insertions(+), 263 deletions(-) diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 4bb54919ea..b57b65086c 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -4,18 +4,28 @@ package tstate import ( "context" + "crypto/rand" + "fmt" "testing" "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/hypersdk/trace" - "github.com/stretchr/testify/require" ) -var ( - TestKey = []byte("key") - TestVal = []byte("value") -) +// import ( +// "context" +// "testing" + +// "github.com/ava-labs/avalanchego/database" +// "github.com/ava-labs/hypersdk/trace" + +// "github.com/stretchr/testify/require" +// ) + +// var ( +// TestKey = []byte("key") +// TestVal = []byte("value") +// ) type TestDB struct { storage map[string][]byte @@ -45,281 +55,322 @@ func (db *TestDB) Remove(_ context.Context, key []byte) error { return nil } -func TestGetValue(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10) - // GetValue without Scope perm - _, err := ts.GetValue(ctx, TestKey) - require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") - // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) - val, err := ts.GetValue(ctx, TestKey) - require.NoError(err, "Error getting value.") - require.Equal(TestVal, val, "Value was not saved correctly.") -} +// func TestGetValue(t *testing.T) { +// require := require.New(t) +// ctx := context.TODO() +// ts := New(10) +// // GetValue without Scope perm +// _, err := ts.GetValue(ctx, TestKey) +// require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") +// // SetScope +// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) +// val, err := ts.GetValue(ctx, TestKey) +// require.NoError(err, "Error getting value.") +// require.Equal(TestVal, val, "Value was not saved correctly.") +// } -func TestGetValueNoStorage(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10) - // SetScope but dont add to storage - ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) - _, err := ts.GetValue(ctx, TestKey) - require.ErrorIs(database.ErrNotFound, err, "No error thrown.") -} +// func TestGetValueNoStorage(t *testing.T) { +// require := require.New(t) +// ctx := context.TODO() +// ts := New(10) +// // SetScope but dont add to storage +// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) +// _, err := ts.GetValue(ctx, TestKey) +// require.ErrorIs(database.ErrNotFound, err, "No error thrown.") +// } -func TestInsertNew(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10) - // Insert before SetScope - err := ts.Insert(ctx, TestKey, TestVal) - require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") - // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) - // Insert key - err = ts.Insert(ctx, TestKey, TestVal) - require.NoError(err, "Error thrown.") - val, err := ts.GetValue(ctx, TestKey) - require.NoError(err, "Error thrown.") - require.Equal(1, ts.OpIndex(), "Insert operation was not added.") - require.Equal(TestVal, val, "Value was not set correctly.") -} +// func TestInsertNew(t *testing.T) { +// require := require.New(t) +// ctx := context.TODO() +// ts := New(10) +// // Insert before SetScope +// err := ts.Insert(ctx, TestKey, TestVal) +// require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") +// // SetScope +// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) +// // Insert key +// err = ts.Insert(ctx, TestKey, TestVal) +// require.NoError(err, "Error thrown.") +// val, err := ts.GetValue(ctx, TestKey) +// require.NoError(err, "Error thrown.") +// require.Equal(1, ts.OpIndex(), "Insert operation was not added.") +// require.Equal(TestVal, val, "Value was not set correctly.") +// } -func TestInsertUpdate(t *testing.T) { - require := require.New(t) - ctx := context.TODO() - ts := New(10) - // SetScope and add - ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) - require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") - // Insert key - newVal := []byte("newVal") - err := ts.Insert(ctx, TestKey, newVal) - require.NoError(err, "Error thrown.") - val, err := ts.GetValue(ctx, TestKey) - require.NoError(err, "Error thrown.") - require.Equal(1, ts.OpIndex(), "Insert operation was not added.") - require.Equal(newVal, val, "Value was not set correctly.") - require.Equal(TestVal, ts.ops[0].pastV, "PastVal was not set correctly.") - require.False(ts.ops[0].pastChanged, "PastVal was not set correctly.") - require.True(ts.ops[0].pastExists, "PastVal was not set correctly.") -} +// func TestInsertUpdate(t *testing.T) { +// require := require.New(t) +// ctx := context.TODO() +// ts := New(10) +// // SetScope and add +// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) +// require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") +// // Insert key +// newVal := []byte("newVal") +// err := ts.Insert(ctx, TestKey, newVal) +// require.NoError(err, "Error thrown.") +// val, err := ts.GetValue(ctx, TestKey) +// require.NoError(err, "Error thrown.") +// require.Equal(1, ts.OpIndex(), "Insert operation was not added.") +// require.Equal(newVal, val, "Value was not set correctly.") +// require.Equal(TestVal, ts.ops[0].pastV, "PastVal was not set correctly.") +// require.False(ts.ops[0].pastChanged, "PastVal was not set correctly.") +// require.True(ts.ops[0].pastExists, "PastVal was not set correctly.") +// } -func TestFetchAndSetScope(t *testing.T) { - require := require.New(t) - ts := New(10) - db := NewTestDB() - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - for i, key := range keys { - err := db.Insert(ctx, key, vals[i]) - require.NoError(err, "Error during insert.") - } - err := ts.FetchAndSetScope(ctx, keys, db) - require.NoError(err, "Error thrown.") - require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") - require.Equal(keys, ts.scope, "Scope not updated correctly.") - // Check values - for i, key := range keys { - val, err := ts.GetValue(ctx, key) - require.NoError(err, "Error getting value.") - require.Equal(vals[i], val, "Value not set correctly.") - } -} +// func TestFetchAndSetScope(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// db := NewTestDB() +// ctx := context.TODO() +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} +// for i, key := range keys { +// err := db.Insert(ctx, key, vals[i]) +// require.NoError(err, "Error during insert.") +// } +// err := ts.FetchAndSetScope(ctx, keys, db) +// require.NoError(err, "Error thrown.") +// require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") +// require.Equal(keys, ts.scope, "Scope not updated correctly.") +// // Check values +// for i, key := range keys { +// val, err := ts.GetValue(ctx, key) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[i], val, "Value not set correctly.") +// } +// } -func TestFetchAndSetScopeMissingKey(t *testing.T) { - require := require.New(t) - ts := New(10) - db := NewTestDB() - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - // Keys[3] not in db - for i, key := range keys[:len(keys)-1] { - err := db.Insert(ctx, key, vals[i]) - require.NoError(err, "Error during insert.") - } - err := ts.FetchAndSetScope(ctx, keys, db) - require.NoError(err, "Error thrown.") - require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") - require.Equal(keys, ts.scope, "Scope not updated correctly.") - // Check values - for i, key := range keys[:len(keys)-1] { - val, err := ts.GetValue(ctx, key) - require.NoError(err, "Error getting value.") - require.Equal(vals[i], val, "Value not set correctly.") - } - _, err = ts.GetValue(ctx, keys[2]) - require.ErrorIs(err, database.ErrNotFound, "Didn't throw correct erro.") -} +// func TestFetchAndSetScopeMissingKey(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// db := NewTestDB() +// ctx := context.TODO() +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} +// // Keys[3] not in db +// for i, key := range keys[:len(keys)-1] { +// err := db.Insert(ctx, key, vals[i]) +// require.NoError(err, "Error during insert.") +// } +// err := ts.FetchAndSetScope(ctx, keys, db) +// require.NoError(err, "Error thrown.") +// require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") +// require.Equal(keys, ts.scope, "Scope not updated correctly.") +// // Check values +// for i, key := range keys[:len(keys)-1] { +// val, err := ts.GetValue(ctx, key) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[i], val, "Value not set correctly.") +// } +// _, err = ts.GetValue(ctx, keys[2]) +// require.ErrorIs(err, database.ErrNotFound, "Didn't throw correct erro.") +// } -func TestSetScope(t *testing.T) { - require := require.New(t) - ts := New(10) - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - ts.SetScope(ctx, keys, map[string][]byte{}) - require.Equal(keys, ts.scope, "Scope not updated correctly.") -} +// func TestSetScope(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// ctx := context.TODO() +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// ts.SetScope(ctx, keys, map[string][]byte{}) +// require.Equal(keys, ts.scope, "Scope not updated correctly.") +// } -func TestRemoveInsertRollback(t *testing.T) { - require := require.New(t) - ts := New(10) - ctx := context.TODO() - ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) - // Insert - err := ts.Insert(ctx, TestKey, TestVal) - require.NoError(err, "Error from insert.") - v, err := ts.GetValue(ctx, TestKey) - require.NoError(err) - require.Equal(TestVal, v) - require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") - // Remove - err = ts.Remove(ctx, TestKey) - require.NoError(err, "Error from remove.") - _, err = ts.GetValue(ctx, TestKey) - require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") - require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") - // Insert - err = ts.Insert(ctx, TestKey, TestVal) - require.NoError(err, "Error from insert.") - v, err = ts.GetValue(ctx, TestKey) - require.NoError(err) - require.Equal(TestVal, v) - require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") - require.Equal(1, ts.PendingChanges()) - // Rollback - ts.Rollback(ctx, 2) - _, err = ts.GetValue(ctx, TestKey) - require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") - // Rollback - ts.Rollback(ctx, 1) - v, err = ts.GetValue(ctx, TestKey) - require.NoError(err) - require.Equal(TestVal, v) -} +// func TestRemoveInsertRollback(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// ctx := context.TODO() +// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) +// // Insert +// err := ts.Insert(ctx, TestKey, TestVal) +// require.NoError(err, "Error from insert.") +// v, err := ts.GetValue(ctx, TestKey) +// require.NoError(err) +// require.Equal(TestVal, v) +// require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") +// // Remove +// err = ts.Remove(ctx, TestKey) +// require.NoError(err, "Error from remove.") +// _, err = ts.GetValue(ctx, TestKey) +// require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") +// require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") +// // Insert +// err = ts.Insert(ctx, TestKey, TestVal) +// require.NoError(err, "Error from insert.") +// v, err = ts.GetValue(ctx, TestKey) +// require.NoError(err) +// require.Equal(TestVal, v) +// require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") +// require.Equal(1, ts.PendingChanges()) +// // Rollback +// ts.Rollback(ctx, 2) +// _, err = ts.GetValue(ctx, TestKey) +// require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") +// // Rollback +// ts.Rollback(ctx, 1) +// v, err = ts.GetValue(ctx, TestKey) +// require.NoError(err) +// require.Equal(TestVal, v) +// } -func TestRemoveNotInScope(t *testing.T) { - require := require.New(t) - ts := New(10) - ctx := context.TODO() - // Remove - err := ts.Remove(ctx, TestKey) - require.ErrorIs(err, ErrKeyNotSpecified, "ErrKeyNotSpecified should be thrown.") -} +// func TestRemoveNotInScope(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// ctx := context.TODO() +// // Remove +// err := ts.Remove(ctx, TestKey) +// require.ErrorIs(err, ErrKeyNotSpecified, "ErrKeyNotSpecified should be thrown.") +// } -func TestRestoreInsert(t *testing.T) { - require := require.New(t) - ts := New(10) - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[string][]byte{}) - for i, key := range keys { - err := ts.Insert(ctx, key, vals[i]) - require.NoError(err, "Error inserting.") - } - updatedVal := []byte("newVal") - err := ts.Insert(ctx, keys[0], updatedVal) - require.NoError(err, "Error inserting.") - require.Equal(len(keys)+1, ts.OpIndex(), "Operations not added properly.") - val, err := ts.GetValue(ctx, keys[0]) - require.NoError(err, "Error getting value.") - require.Equal(updatedVal, val, "Value not updated correctly.") - // Rollback inserting updatedVal and key[2] - ts.Rollback(ctx, 2) - require.Equal(2, ts.OpIndex(), "Operations not rolled back properly.") - // Keys[2] was removed - _, err = ts.GetValue(ctx, keys[2]) - require.ErrorIs(err, database.ErrNotFound, "TState read op not rolled back properly.") - // Keys[0] was set to past value - val, err = ts.GetValue(ctx, keys[0]) - require.NoError(err, "Error getting value.") - require.Equal(vals[0], val, "Value not rolled back properly.") -} +// func TestRestoreInsert(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// ctx := context.TODO() +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} +// ts.SetScope(ctx, keys, map[string][]byte{}) +// for i, key := range keys { +// err := ts.Insert(ctx, key, vals[i]) +// require.NoError(err, "Error inserting.") +// } +// updatedVal := []byte("newVal") +// err := ts.Insert(ctx, keys[0], updatedVal) +// require.NoError(err, "Error inserting.") +// require.Equal(len(keys)+1, ts.OpIndex(), "Operations not added properly.") +// val, err := ts.GetValue(ctx, keys[0]) +// require.NoError(err, "Error getting value.") +// require.Equal(updatedVal, val, "Value not updated correctly.") +// // Rollback inserting updatedVal and key[2] +// ts.Rollback(ctx, 2) +// require.Equal(2, ts.OpIndex(), "Operations not rolled back properly.") +// // Keys[2] was removed +// _, err = ts.GetValue(ctx, keys[2]) +// require.ErrorIs(err, database.ErrNotFound, "TState read op not rolled back properly.") +// // Keys[0] was set to past value +// val, err = ts.GetValue(ctx, keys[0]) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[0], val, "Value not rolled back properly.") +// } -func TestRestoreDelete(t *testing.T) { - require := require.New(t) - ts := New(10) - ctx := context.TODO() - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[string][]byte{ - string(keys[0]): vals[0], - string(keys[1]): vals[1], - string(keys[2]): vals[2], - }) - // Check scope - for i, key := range keys { - val, err := ts.GetValue(ctx, key) - require.NoError(err, "Error getting value.") - require.Equal(vals[i], val, "Value not set correctly.") - } - // Remove all - for _, key := range keys { - err := ts.Remove(ctx, key) - require.NoError(err, "Error removing from ts.") - _, err = ts.GetValue(ctx, key) - require.ErrorIs(err, database.ErrNotFound, "Value not removed.") - } - require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") - require.Equal(3, ts.PendingChanges()) - // Roll back all removes - ts.Rollback(ctx, 0) - require.Equal(0, ts.OpIndex(), "Operations not rolled back properly.") - require.Equal(0, ts.PendingChanges()) - for i, key := range keys { - val, err := ts.GetValue(ctx, key) - require.NoError(err, "Error getting value.") - require.Equal(vals[i], val, "Value not reset correctly.") +// func TestRestoreDelete(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// ctx := context.TODO() +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} +// ts.SetScope(ctx, keys, map[string][]byte{ +// string(keys[0]): vals[0], +// string(keys[1]): vals[1], +// string(keys[2]): vals[2], +// }) +// // Check scope +// for i, key := range keys { +// val, err := ts.GetValue(ctx, key) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[i], val, "Value not set correctly.") +// } +// // Remove all +// for _, key := range keys { +// err := ts.Remove(ctx, key) +// require.NoError(err, "Error removing from ts.") +// _, err = ts.GetValue(ctx, key) +// require.ErrorIs(err, database.ErrNotFound, "Value not removed.") +// } +// require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") +// require.Equal(3, ts.PendingChanges()) +// // Roll back all removes +// ts.Rollback(ctx, 0) +// require.Equal(0, ts.OpIndex(), "Operations not rolled back properly.") +// require.Equal(0, ts.PendingChanges()) +// for i, key := range keys { +// val, err := ts.GetValue(ctx, key) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[i], val, "Value not reset correctly.") +// } +// } + +// func TestWriteChanges(t *testing.T) { +// require := require.New(t) +// ts := New(10) +// db := NewTestDB() +// ctx := context.TODO() +// tracer, _ := trace.New(&trace.Config{Enabled: false}) +// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} +// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} +// ts.SetScope(ctx, keys, map[string][]byte{}) +// // Add +// for i, key := range keys { +// err := ts.Insert(ctx, key, vals[i]) +// require.NoError(err, "Error inserting value.") +// val, err := ts.GetValue(ctx, key) +// require.NoError(err, "Error getting value.") +// require.Equal(vals[i], val, "Value not set correctly.") +// } +// err := ts.WriteChanges(ctx, db, tracer) +// require.NoError(err, "Error writing changes.") +// // Check if db was updated correctly +// for i, key := range keys { +// val, _ := db.GetValue(ctx, key) +// require.Equal(vals[i], val, "Value not updated in db.") +// } +// // Remove +// ts = New(10) +// ts.SetScope(ctx, keys, map[string][]byte{ +// string(keys[0]): vals[0], +// string(keys[1]): vals[1], +// string(keys[2]): vals[2], +// }) +// for _, key := range keys { +// err := ts.Remove(ctx, key) +// require.NoError(err, "Error removing from ts.") +// _, err = ts.GetValue(ctx, key) +// require.ErrorIs(err, database.ErrNotFound, "Key not removed.") +// } +// err = ts.WriteChanges(ctx, db, tracer) +// require.NoError(err, "Error writing changes.") +// // Check if db was updated correctly +// for _, key := range keys { +// _, err := db.GetValue(ctx, key) +// require.ErrorIs(err, database.ErrNotFound, "Value not removed from db.") +// } +// } + + +func BenchmarkFetchAndSetScope(b *testing.B) { + for _, size := range []int{1, 5, 10, 15, 20} { + b.Run(fmt.Sprintf("get_validator_set_%d_validators", size), func(b *testing.B) { + benchmarkGetValidatorSet(b, size) + }) } } -func TestWriteChanges(t *testing.T) { - require := require.New(t) +func benchmarkGetValidatorSet(b *testing.B, size int) { + require := require.New(b) ts := New(10) db := NewTestDB() ctx := context.TODO() - tracer, _ := trace.New(&trace.Config{Enabled: false}) - keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[string][]byte{}) - // Add - for i, key := range keys { - err := ts.Insert(ctx, key, vals[i]) - require.NoError(err, "Error inserting value.") - val, err := ts.GetValue(ctx, key) - require.NoError(err, "Error getting value.") - require.Equal(vals[i], val, "Value not set correctly.") + + keys := [][]byte{} + vals := [][]byte{} + for range "0..size" { + keys = append(keys, randomBytes(65)) + vals = append(vals, randomBytes(8)) } - err := ts.WriteChanges(ctx, db, tracer) - require.NoError(err, "Error writing changes.") - // Check if db was updated correctly + for i, key := range keys { - val, _ := db.GetValue(ctx, key) - require.Equal(vals[i], val, "Value not updated in db.") - } - // Remove - ts = New(10) - ts.SetScope(ctx, keys, map[string][]byte{ - string(keys[0]): vals[0], - string(keys[1]): vals[1], - string(keys[2]): vals[2], - }) - for _, key := range keys { - err := ts.Remove(ctx, key) - require.NoError(err, "Error removing from ts.") - _, err = ts.GetValue(ctx, key) - require.ErrorIs(err, database.ErrNotFound, "Key not removed.") + err := db.Insert(ctx, key, vals[i]) + require.NoError(err, "Error during insert.") } - err = ts.WriteChanges(ctx, db, tracer) - require.NoError(err, "Error writing changes.") - // Check if db was updated correctly - for _, key := range keys { - _, err := db.GetValue(ctx, key) - require.ErrorIs(err, database.ErrNotFound, "Value not removed from db.") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + err := ts.FetchAndSetScope(ctx, keys, db) + require.NoError(err) } + b.StopTimer() +} + +func randomBytes(size int) []byte { + bytes := make([]byte, size) + rand.Read(bytes) + return bytes } From 884d441e8f9fc968f933237dd1bd0c227bed69b3 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 4 May 2023 16:53:30 -0400 Subject: [PATCH 02/10] wip Signed-off-by: Sam Batschelet --- chain/processor.go | 19 +++++++------- tstate/tstate.go | 60 ++++++++++++++++++++++++------------------- tstate/tstate_test.go | 10 +++++--- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index 0db5af72e0..3824248838 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -20,7 +20,7 @@ type fetchData struct { type txData struct { tx *Transaction - storage map[string][]byte + storage map[[65]byte][]byte } type Processor struct { @@ -49,26 +49,27 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := make(map[string]*fetchData, len(p.blk.GetTxs())) + alreadyFetched := make(map[[65]byte]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { - storage := map[string][]byte{} + storage := map[[65]byte][]byte{} for _, k := range tx.StateKeys(sm) { - sk := string(k) - if v, ok := alreadyFetched[sk]; ok { + var ary [65]byte + copy(ary[:], k) + if v, ok := alreadyFetched[ary]; ok { if v.exists { - storage[sk] = v.v + storage[ary] = v.v } continue } v, err := db.GetValue(ctx, k) if errors.Is(err, database.ErrNotFound) { - alreadyFetched[sk] = &fetchData{nil, false} + alreadyFetched[ary] = &fetchData{nil, false} continue } else if err != nil { panic(err) } - alreadyFetched[sk] = &fetchData{v, true} - storage[sk] = v + alreadyFetched[ary] = &fetchData{v, true} + storage[ary] = v } p.readyTxs <- &txData{tx, storage} } diff --git a/tstate/tstate.go b/tstate/tstate.go index 76ef7ccd7b..799af09171 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -15,7 +15,7 @@ import ( ) type op struct { - k string + k [65]byte pastExists bool pastV []byte @@ -34,14 +34,14 @@ type cacheItem struct { // TState defines a struct for storing temporary state. type TState struct { - changedKeys map[string]*tempStorage - fetchCache map[string]*cacheItem // in case we evict and want to re-fetch + changedKeys map[[65]byte]*tempStorage + fetchCache map[[65]byte]*cacheItem // in case we evict and want to re-fetch // We don't differentiate between read and write scope because it is very // uncommon for a user to write something without first reading what is // there. scope [][]byte // stores a list of managed keys in the TState struct - scopeStorage map[string][]byte + scopeStorage map[[65]byte][]byte // Ops is a record of all operations performed on [TState]. Tracking // operations allows for reverting state to a certain point-in-time. @@ -52,9 +52,9 @@ type TState struct { // maps to have an initial size of [storageSize] and [changedSize] respectively. func New(changedSize int) *TState { return &TState{ - changedKeys: make(map[string]*tempStorage, changedSize), + changedKeys: make(map[[65]byte]*tempStorage, changedSize), - fetchCache: map[string]*cacheItem{}, + fetchCache: map[[65]byte]*cacheItem{}, ops: make([]*op, 0, changedSize), } @@ -67,15 +67,16 @@ func (ts *TState) GetValue(ctx context.Context, key []byte) ([]byte, error) { if !ts.checkScope(ctx, key) { return nil, ErrKeyNotSpecified } - k := string(key) - v, _, exists := ts.getValue(ctx, k) + var ary [65]byte + copy(ary[:], key) + v, _, exists := ts.getValue(ctx, ary) if !exists { return nil, database.ErrNotFound } return v, nil } -func (ts *TState) getValue(_ context.Context, key string) ([]byte, bool, bool) { +func (ts *TState) getValue(_ context.Context, key [65]byte) ([]byte, bool, bool) { if v, ok := ts.changedKeys[key]; ok { if v.removed { return nil, true, false @@ -89,36 +90,39 @@ func (ts *TState) getValue(_ context.Context, key string) ([]byte, bool, bool) { return v, false, true } +type Array [65]byte + // FetchAndSetScope updates ts to include the [db] values associated with [keys]. // FetchAndSetScope then sets the scope of ts to [keys]. If a key exists in // ts.fetchCache set the key's value to the value from cache. func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Database) error { - ts.scopeStorage = map[string][]byte{} + ts.scopeStorage = map[[65]byte][]byte{} for _, key := range keys { - k := string(key) - if val, ok := ts.fetchCache[k]; ok { + var ary [65]byte + copy(ary[:], key) + if val, ok := ts.fetchCache[ary]; ok { if val.Exists { - ts.scopeStorage[k] = val.Value + ts.scopeStorage[ary] = val.Value } continue } v, err := db.GetValue(ctx, key) if errors.Is(err, database.ErrNotFound) { - ts.fetchCache[k] = &cacheItem{Exists: false} + ts.fetchCache[ary] = &cacheItem{Exists: false} continue } if err != nil { return err } - ts.fetchCache[k] = &cacheItem{Value: v, Exists: true} - ts.scopeStorage[k] = v + ts.fetchCache[ary] = &cacheItem{Value: v, Exists: true} + ts.scopeStorage[ary] = v } ts.scope = keys return nil } // SetReadScope sets the readscope of ts to [keys]. -func (ts *TState) SetScope(_ context.Context, keys [][]byte, storage map[string][]byte) { +func (ts *TState) SetScope(_ context.Context, keys [][]byte, storage map[[65]byte][]byte) { ts.scope = keys ts.scopeStorage = storage } @@ -139,15 +143,16 @@ func (ts *TState) Insert(ctx context.Context, key []byte, value []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - k := string(key) - past, changed, exists := ts.getValue(ctx, k) + var ary [65]byte + copy(ary[:], key) + past, changed, exists := ts.getValue(ctx, ary) ts.ops = append(ts.ops, &op{ - k: k, + k: ary, pastExists: exists, pastV: past, pastChanged: changed, }) - ts.changedKeys[k] = &tempStorage{value, false} + ts.changedKeys[ary] = &tempStorage{value, false} return nil } @@ -156,18 +161,19 @@ func (ts *TState) Remove(ctx context.Context, key []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - k := string(key) - past, changed, exists := ts.getValue(ctx, k) + var ary [65]byte + copy(ary[:], key) + past, changed, exists := ts.getValue(ctx, ary) if !exists { return nil } ts.ops = append(ts.ops, &op{ - k: k, + k: ary, pastExists: true, pastV: past, pastChanged: changed, }) - ts.changedKeys[k] = &tempStorage{nil, true} + ts.changedKeys[ary] = &tempStorage{nil, true} return nil } @@ -216,12 +222,12 @@ func (ts *TState) WriteChanges( for key, tstorage := range ts.changedKeys { if !tstorage.removed { - if err := db.Insert(ctx, []byte(key), tstorage.v); err != nil { + if err := db.Insert(ctx, key[:], tstorage.v); err != nil { return err } continue } - if err := db.Remove(ctx, []byte(key)); err != nil { + if err := db.Remove(ctx, key[:]); err != nil { return err } } diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index b57b65086c..5ed5018c67 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -336,7 +336,7 @@ func (db *TestDB) Remove(_ context.Context, key []byte) error { func BenchmarkFetchAndSetScope(b *testing.B) { - for _, size := range []int{1, 5, 10, 15, 20} { + for _, size := range []int{100, 1000, 10000} { b.Run(fmt.Sprintf("get_validator_set_%d_validators", size), func(b *testing.B) { benchmarkGetValidatorSet(b, size) }) @@ -345,14 +345,17 @@ func BenchmarkFetchAndSetScope(b *testing.B) { func benchmarkGetValidatorSet(b *testing.B, size int) { require := require.New(b) - ts := New(10) + ts := New(1000) db := NewTestDB() ctx := context.TODO() keys := [][]byte{} vals := [][]byte{} + + k := randomBytes(65); + for range "0..size" { - keys = append(keys, randomBytes(65)) + keys = append(keys, k) vals = append(vals, randomBytes(8)) } @@ -366,6 +369,7 @@ func benchmarkGetValidatorSet(b *testing.B, size int) { err := ts.FetchAndSetScope(ctx, keys, db) require.NoError(err) } + b.ReportAllocs() b.StopTimer() } From 3caa306100d7f829ed2d8213bd9a319a46b7ada2 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 4 May 2023 17:40:09 -0400 Subject: [PATCH 03/10] [tstate] change map key from string to fixed size byte array Signed-off-by: Sam Batschelet --- tstate/tstate.go | 45 ++-- tstate/tstate_test.go | 567 +++++++++++++++++++++--------------------- 2 files changed, 301 insertions(+), 311 deletions(-) diff --git a/tstate/tstate.go b/tstate/tstate.go index 799af09171..659c1a999f 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -67,9 +67,7 @@ func (ts *TState) GetValue(ctx context.Context, key []byte) ([]byte, error) { if !ts.checkScope(ctx, key) { return nil, ErrKeyNotSpecified } - var ary [65]byte - copy(ary[:], key) - v, _, exists := ts.getValue(ctx, ary) + v, _, exists := ts.getValue(ctx, newFixedSizeByteArray(key)) if !exists { return nil, database.ErrNotFound } @@ -90,32 +88,30 @@ func (ts *TState) getValue(_ context.Context, key [65]byte) ([]byte, bool, bool) return v, false, true } -type Array [65]byte - // FetchAndSetScope updates ts to include the [db] values associated with [keys]. // FetchAndSetScope then sets the scope of ts to [keys]. If a key exists in // ts.fetchCache set the key's value to the value from cache. func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Database) error { ts.scopeStorage = map[[65]byte][]byte{} + for _, key := range keys { - var ary [65]byte - copy(ary[:], key) - if val, ok := ts.fetchCache[ary]; ok { + k := newFixedSizeByteArray(key) + if val, ok := ts.fetchCache[k]; ok { if val.Exists { - ts.scopeStorage[ary] = val.Value + ts.scopeStorage[k] = val.Value } continue } v, err := db.GetValue(ctx, key) if errors.Is(err, database.ErrNotFound) { - ts.fetchCache[ary] = &cacheItem{Exists: false} + ts.fetchCache[k] = &cacheItem{Exists: false} continue } if err != nil { return err } - ts.fetchCache[ary] = &cacheItem{Value: v, Exists: true} - ts.scopeStorage[ary] = v + ts.fetchCache[k] = &cacheItem{Value: v, Exists: true} + ts.scopeStorage[k] = v } ts.scope = keys return nil @@ -143,16 +139,15 @@ func (ts *TState) Insert(ctx context.Context, key []byte, value []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - var ary [65]byte - copy(ary[:], key) - past, changed, exists := ts.getValue(ctx, ary) + k := newFixedSizeByteArray(key) + past, changed, exists := ts.getValue(ctx, k) ts.ops = append(ts.ops, &op{ - k: ary, + k: k, pastExists: exists, pastV: past, pastChanged: changed, }) - ts.changedKeys[ary] = &tempStorage{value, false} + ts.changedKeys[k] = &tempStorage{value, false} return nil } @@ -161,19 +156,19 @@ func (ts *TState) Remove(ctx context.Context, key []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - var ary [65]byte - copy(ary[:], key) - past, changed, exists := ts.getValue(ctx, ary) + + k := newFixedSizeByteArray(key) + past, changed, exists := ts.getValue(ctx, k) if !exists { return nil } ts.ops = append(ts.ops, &op{ - k: ary, + k: k, pastExists: true, pastV: past, pastChanged: changed, }) - ts.changedKeys[ary] = &tempStorage{nil, true} + ts.changedKeys[k] = &tempStorage{nil, true} return nil } @@ -233,3 +228,9 @@ func (ts *TState) WriteChanges( } return nil } + +func newFixedSizeByteArray(key []byte) [65]byte { + var k [65]byte + copy(k[:], key) + return k +} diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 5ed5018c67..70d468c8ac 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -9,23 +9,14 @@ import ( "testing" "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/hypersdk/trace" "github.com/stretchr/testify/require" ) -// import ( -// "context" -// "testing" - -// "github.com/ava-labs/avalanchego/database" -// "github.com/ava-labs/hypersdk/trace" - -// "github.com/stretchr/testify/require" -// ) - -// var ( -// TestKey = []byte("key") -// TestVal = []byte("value") -// ) +var ( + TestKey = []byte("key") + TestVal = []byte("value") +) type TestDB struct { storage map[string][]byte @@ -55,307 +46,305 @@ func (db *TestDB) Remove(_ context.Context, key []byte) error { return nil } -// func TestGetValue(t *testing.T) { -// require := require.New(t) -// ctx := context.TODO() -// ts := New(10) -// // GetValue without Scope perm -// _, err := ts.GetValue(ctx, TestKey) -// require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") -// // SetScope -// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) -// val, err := ts.GetValue(ctx, TestKey) -// require.NoError(err, "Error getting value.") -// require.Equal(TestVal, val, "Value was not saved correctly.") -// } - -// func TestGetValueNoStorage(t *testing.T) { -// require := require.New(t) -// ctx := context.TODO() -// ts := New(10) -// // SetScope but dont add to storage -// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) -// _, err := ts.GetValue(ctx, TestKey) -// require.ErrorIs(database.ErrNotFound, err, "No error thrown.") -// } +func TestGetValue(t *testing.T) { + require := require.New(t) + ctx := context.TODO() + ts := New(10) + // GetValue without Scope perm + _, err := ts.GetValue(ctx, TestKey) + require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") + // SetScope + ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{newFixedSizeByteArray(TestKey): TestVal}) + val, err := ts.GetValue(ctx, TestKey) + require.NoError(err, "Error getting value.") + require.Equal(TestVal, val, "Value was not saved correctly.") +} -// func TestInsertNew(t *testing.T) { -// require := require.New(t) -// ctx := context.TODO() -// ts := New(10) -// // Insert before SetScope -// err := ts.Insert(ctx, TestKey, TestVal) -// require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") -// // SetScope -// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) -// // Insert key -// err = ts.Insert(ctx, TestKey, TestVal) -// require.NoError(err, "Error thrown.") -// val, err := ts.GetValue(ctx, TestKey) -// require.NoError(err, "Error thrown.") -// require.Equal(1, ts.OpIndex(), "Insert operation was not added.") -// require.Equal(TestVal, val, "Value was not set correctly.") -// } +func TestGetValueNoStorage(t *testing.T) { + require := require.New(t) + ctx := context.TODO() + ts := New(10) + // SetScope but dont add to storage + ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + _, err := ts.GetValue(ctx, TestKey) + require.ErrorIs(database.ErrNotFound, err, "No error thrown.") +} -// func TestInsertUpdate(t *testing.T) { -// require := require.New(t) -// ctx := context.TODO() -// ts := New(10) -// // SetScope and add -// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{string(TestKey): TestVal}) -// require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") -// // Insert key -// newVal := []byte("newVal") -// err := ts.Insert(ctx, TestKey, newVal) -// require.NoError(err, "Error thrown.") -// val, err := ts.GetValue(ctx, TestKey) -// require.NoError(err, "Error thrown.") -// require.Equal(1, ts.OpIndex(), "Insert operation was not added.") -// require.Equal(newVal, val, "Value was not set correctly.") -// require.Equal(TestVal, ts.ops[0].pastV, "PastVal was not set correctly.") -// require.False(ts.ops[0].pastChanged, "PastVal was not set correctly.") -// require.True(ts.ops[0].pastExists, "PastVal was not set correctly.") -// } +func TestInsertNew(t *testing.T) { + require := require.New(t) + ctx := context.TODO() + ts := New(10) + // Insert before SetScope + err := ts.Insert(ctx, TestKey, TestVal) + require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") + // SetScope + ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + // Insert key + err = ts.Insert(ctx, TestKey, TestVal) + require.NoError(err, "Error thrown.") + val, err := ts.GetValue(ctx, TestKey) + require.NoError(err, "Error thrown.") + require.Equal(1, ts.OpIndex(), "Insert operation was not added.") + require.Equal(TestVal, val, "Value was not set correctly.") +} -// func TestFetchAndSetScope(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// db := NewTestDB() -// ctx := context.TODO() -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} -// for i, key := range keys { -// err := db.Insert(ctx, key, vals[i]) -// require.NoError(err, "Error during insert.") -// } -// err := ts.FetchAndSetScope(ctx, keys, db) -// require.NoError(err, "Error thrown.") -// require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") -// require.Equal(keys, ts.scope, "Scope not updated correctly.") -// // Check values -// for i, key := range keys { -// val, err := ts.GetValue(ctx, key) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[i], val, "Value not set correctly.") -// } -// } +func TestInsertUpdate(t *testing.T) { + require := require.New(t) + ctx := context.TODO() + ts := New(10) + // SetScope and add + ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{newFixedSizeByteArray(TestKey): TestVal}) + require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") + // Insert key + newVal := []byte("newVal") + err := ts.Insert(ctx, TestKey, newVal) + require.NoError(err, "Error thrown.") + val, err := ts.GetValue(ctx, TestKey) + require.NoError(err, "Error thrown.") + require.Equal(1, ts.OpIndex(), "Insert operation was not added.") + require.Equal(newVal, val, "Value was not set correctly.") + require.Equal(TestVal, ts.ops[0].pastV, "PastVal was not set correctly.") + require.False(ts.ops[0].pastChanged, "PastVal was not set correctly.") + require.True(ts.ops[0].pastExists, "PastVal was not set correctly.") +} -// func TestFetchAndSetScopeMissingKey(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// db := NewTestDB() -// ctx := context.TODO() -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} -// // Keys[3] not in db -// for i, key := range keys[:len(keys)-1] { -// err := db.Insert(ctx, key, vals[i]) -// require.NoError(err, "Error during insert.") -// } -// err := ts.FetchAndSetScope(ctx, keys, db) -// require.NoError(err, "Error thrown.") -// require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") -// require.Equal(keys, ts.scope, "Scope not updated correctly.") -// // Check values -// for i, key := range keys[:len(keys)-1] { -// val, err := ts.GetValue(ctx, key) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[i], val, "Value not set correctly.") -// } -// _, err = ts.GetValue(ctx, keys[2]) -// require.ErrorIs(err, database.ErrNotFound, "Didn't throw correct erro.") -// } +func TestFetchAndSetScope(t *testing.T) { + require := require.New(t) + ts := New(10) + db := NewTestDB() + ctx := context.TODO() + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} + for i, key := range keys { + err := db.Insert(ctx, key, vals[i]) + require.NoError(err, "Error during insert.") + } + err := ts.FetchAndSetScope(ctx, keys, db) + require.NoError(err, "Error thrown.") + require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(keys, ts.scope, "Scope not updated correctly.") + // Check values + for i, key := range keys { + val, err := ts.GetValue(ctx, key) + require.NoError(err, "Error getting value.") + require.Equal(vals[i], val, "Value not set correctly.") + } +} -// func TestSetScope(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// ctx := context.TODO() -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// ts.SetScope(ctx, keys, map[string][]byte{}) -// require.Equal(keys, ts.scope, "Scope not updated correctly.") -// } +func TestFetchAndSetScopeMissingKey(t *testing.T) { + require := require.New(t) + ts := New(10) + db := NewTestDB() + ctx := context.TODO() + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} + // Keys[3] not in db + for i, key := range keys[:len(keys)-1] { + err := db.Insert(ctx, key, vals[i]) + require.NoError(err, "Error during insert.") + } + err := ts.FetchAndSetScope(ctx, keys, db) + require.NoError(err, "Error thrown.") + require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(keys, ts.scope, "Scope not updated correctly.") + // Check values + for i, key := range keys[:len(keys)-1] { + val, err := ts.GetValue(ctx, key) + require.NoError(err, "Error getting value.") + require.Equal(vals[i], val, "Value not set correctly.") + } + _, err = ts.GetValue(ctx, keys[2]) + require.ErrorIs(err, database.ErrNotFound, "Didn't throw correct erro.") +} -// func TestRemoveInsertRollback(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// ctx := context.TODO() -// ts.SetScope(ctx, [][]byte{TestKey}, map[string][]byte{}) -// // Insert -// err := ts.Insert(ctx, TestKey, TestVal) -// require.NoError(err, "Error from insert.") -// v, err := ts.GetValue(ctx, TestKey) -// require.NoError(err) -// require.Equal(TestVal, v) -// require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") -// // Remove -// err = ts.Remove(ctx, TestKey) -// require.NoError(err, "Error from remove.") -// _, err = ts.GetValue(ctx, TestKey) -// require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") -// require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") -// // Insert -// err = ts.Insert(ctx, TestKey, TestVal) -// require.NoError(err, "Error from insert.") -// v, err = ts.GetValue(ctx, TestKey) -// require.NoError(err) -// require.Equal(TestVal, v) -// require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") -// require.Equal(1, ts.PendingChanges()) -// // Rollback -// ts.Rollback(ctx, 2) -// _, err = ts.GetValue(ctx, TestKey) -// require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") -// // Rollback -// ts.Rollback(ctx, 1) -// v, err = ts.GetValue(ctx, TestKey) -// require.NoError(err) -// require.Equal(TestVal, v) -// } +func TestSetScope(t *testing.T) { + require := require.New(t) + ts := New(10) + ctx := context.TODO() + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + require.Equal(keys, ts.scope, "Scope not updated correctly.") +} -// func TestRemoveNotInScope(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// ctx := context.TODO() -// // Remove -// err := ts.Remove(ctx, TestKey) -// require.ErrorIs(err, ErrKeyNotSpecified, "ErrKeyNotSpecified should be thrown.") -// } +func TestRemoveInsertRollback(t *testing.T) { + require := require.New(t) + ts := New(10) + ctx := context.TODO() + ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + // Insert + err := ts.Insert(ctx, TestKey, TestVal) + require.NoError(err, "Error from insert.") + v, err := ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) + require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") + // Remove + err = ts.Remove(ctx, TestKey) + require.NoError(err, "Error from remove.") + _, err = ts.GetValue(ctx, TestKey) + require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") + require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") + // Insert + err = ts.Insert(ctx, TestKey, TestVal) + require.NoError(err, "Error from insert.") + v, err = ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) + require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(1, ts.PendingChanges()) + // Rollback + ts.Rollback(ctx, 2) + _, err = ts.GetValue(ctx, TestKey) + require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") + // Rollback + ts.Rollback(ctx, 1) + v, err = ts.GetValue(ctx, TestKey) + require.NoError(err) + require.Equal(TestVal, v) +} -// func TestRestoreInsert(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// ctx := context.TODO() -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} -// ts.SetScope(ctx, keys, map[string][]byte{}) -// for i, key := range keys { -// err := ts.Insert(ctx, key, vals[i]) -// require.NoError(err, "Error inserting.") -// } -// updatedVal := []byte("newVal") -// err := ts.Insert(ctx, keys[0], updatedVal) -// require.NoError(err, "Error inserting.") -// require.Equal(len(keys)+1, ts.OpIndex(), "Operations not added properly.") -// val, err := ts.GetValue(ctx, keys[0]) -// require.NoError(err, "Error getting value.") -// require.Equal(updatedVal, val, "Value not updated correctly.") -// // Rollback inserting updatedVal and key[2] -// ts.Rollback(ctx, 2) -// require.Equal(2, ts.OpIndex(), "Operations not rolled back properly.") -// // Keys[2] was removed -// _, err = ts.GetValue(ctx, keys[2]) -// require.ErrorIs(err, database.ErrNotFound, "TState read op not rolled back properly.") -// // Keys[0] was set to past value -// val, err = ts.GetValue(ctx, keys[0]) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[0], val, "Value not rolled back properly.") -// } +func TestRemoveNotInScope(t *testing.T) { + require := require.New(t) + ts := New(10) + ctx := context.TODO() + // Remove + err := ts.Remove(ctx, TestKey) + require.ErrorIs(err, ErrKeyNotSpecified, "ErrKeyNotSpecified should be thrown.") +} -// func TestRestoreDelete(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// ctx := context.TODO() -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} -// ts.SetScope(ctx, keys, map[string][]byte{ -// string(keys[0]): vals[0], -// string(keys[1]): vals[1], -// string(keys[2]): vals[2], -// }) -// // Check scope -// for i, key := range keys { -// val, err := ts.GetValue(ctx, key) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[i], val, "Value not set correctly.") -// } -// // Remove all -// for _, key := range keys { -// err := ts.Remove(ctx, key) -// require.NoError(err, "Error removing from ts.") -// _, err = ts.GetValue(ctx, key) -// require.ErrorIs(err, database.ErrNotFound, "Value not removed.") -// } -// require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") -// require.Equal(3, ts.PendingChanges()) -// // Roll back all removes -// ts.Rollback(ctx, 0) -// require.Equal(0, ts.OpIndex(), "Operations not rolled back properly.") -// require.Equal(0, ts.PendingChanges()) -// for i, key := range keys { -// val, err := ts.GetValue(ctx, key) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[i], val, "Value not reset correctly.") -// } -// } +func TestRestoreInsert(t *testing.T) { + require := require.New(t) + ts := New(10) + ctx := context.TODO() + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} + ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + for i, key := range keys { + err := ts.Insert(ctx, key, vals[i]) + require.NoError(err, "Error inserting.") + } + updatedVal := []byte("newVal") + err := ts.Insert(ctx, keys[0], updatedVal) + require.NoError(err, "Error inserting.") + require.Equal(len(keys)+1, ts.OpIndex(), "Operations not added properly.") + val, err := ts.GetValue(ctx, keys[0]) + require.NoError(err, "Error getting value.") + require.Equal(updatedVal, val, "Value not updated correctly.") + // Rollback inserting updatedVal and key[2] + ts.Rollback(ctx, 2) + require.Equal(2, ts.OpIndex(), "Operations not rolled back properly.") + // Keys[2] was removed + _, err = ts.GetValue(ctx, keys[2]) + require.ErrorIs(err, database.ErrNotFound, "TState read op not rolled back properly.") + // Keys[0] was set to past value + val, err = ts.GetValue(ctx, keys[0]) + require.NoError(err, "Error getting value.") + require.Equal(vals[0], val, "Value not rolled back properly.") +} -// func TestWriteChanges(t *testing.T) { -// require := require.New(t) -// ts := New(10) -// db := NewTestDB() -// ctx := context.TODO() -// tracer, _ := trace.New(&trace.Config{Enabled: false}) -// keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} -// vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} -// ts.SetScope(ctx, keys, map[string][]byte{}) -// // Add -// for i, key := range keys { -// err := ts.Insert(ctx, key, vals[i]) -// require.NoError(err, "Error inserting value.") -// val, err := ts.GetValue(ctx, key) -// require.NoError(err, "Error getting value.") -// require.Equal(vals[i], val, "Value not set correctly.") -// } -// err := ts.WriteChanges(ctx, db, tracer) -// require.NoError(err, "Error writing changes.") -// // Check if db was updated correctly -// for i, key := range keys { -// val, _ := db.GetValue(ctx, key) -// require.Equal(vals[i], val, "Value not updated in db.") -// } -// // Remove -// ts = New(10) -// ts.SetScope(ctx, keys, map[string][]byte{ -// string(keys[0]): vals[0], -// string(keys[1]): vals[1], -// string(keys[2]): vals[2], -// }) -// for _, key := range keys { -// err := ts.Remove(ctx, key) -// require.NoError(err, "Error removing from ts.") -// _, err = ts.GetValue(ctx, key) -// require.ErrorIs(err, database.ErrNotFound, "Key not removed.") -// } -// err = ts.WriteChanges(ctx, db, tracer) -// require.NoError(err, "Error writing changes.") -// // Check if db was updated correctly -// for _, key := range keys { -// _, err := db.GetValue(ctx, key) -// require.ErrorIs(err, database.ErrNotFound, "Value not removed from db.") -// } -// } +func TestRestoreDelete(t *testing.T) { + require := require.New(t) + ts := New(10) + ctx := context.TODO() + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} + ts.SetScope(ctx, keys, map[[65]byte][]byte{ + newFixedSizeByteArray(keys[0]): vals[0], + newFixedSizeByteArray(keys[1]): vals[1], + newFixedSizeByteArray(keys[2]): vals[2], + }) + // Check scope + for i, key := range keys { + val, err := ts.GetValue(ctx, key) + require.NoError(err, "Error getting value.") + require.Equal(vals[i], val, "Value not set correctly.") + } + // Remove all + for _, key := range keys { + err := ts.Remove(ctx, key) + require.NoError(err, "Error removing from ts.") + _, err = ts.GetValue(ctx, key) + require.ErrorIs(err, database.ErrNotFound, "Value not removed.") + } + require.Equal(len(keys), ts.OpIndex(), "Operations not added properly.") + require.Equal(3, ts.PendingChanges()) + // Roll back all removes + ts.Rollback(ctx, 0) + require.Equal(0, ts.OpIndex(), "Operations not rolled back properly.") + require.Equal(0, ts.PendingChanges()) + for i, key := range keys { + val, err := ts.GetValue(ctx, key) + require.NoError(err, "Error getting value.") + require.Equal(vals[i], val, "Value not reset correctly.") + } +} +func TestWriteChanges(t *testing.T) { + require := require.New(t) + ts := New(10) + db := NewTestDB() + ctx := context.TODO() + tracer, _ := trace.New(&trace.Config{Enabled: false}) + keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} + vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} + ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + // Add + for i, key := range keys { + err := ts.Insert(ctx, key, vals[i]) + require.NoError(err, "Error inserting value.") + val, err := ts.GetValue(ctx, key) + require.NoError(err, "Error getting value.") + require.Equal(vals[i], val, "Value not set correctly.") + } + err := ts.WriteChanges(ctx, db, tracer) + require.NoError(err, "Error writing changes.") + // Check if db was updated correctly + for i, key := range keys { + val, _ := db.GetValue(ctx, key) + require.Equal(vals[i], val, "Value not updated in db.") + } + // Remove + ts = New(10) + ts.SetScope(ctx, keys, map[[65]byte][]byte{ + newFixedSizeByteArray(keys[0]): vals[0], + newFixedSizeByteArray(keys[1]): vals[1], + newFixedSizeByteArray(keys[2]): vals[2], + }) + for _, key := range keys { + err := ts.Remove(ctx, key) + require.NoError(err, "Error removing from ts.") + _, err = ts.GetValue(ctx, key) + require.ErrorIs(err, database.ErrNotFound, "Key not removed.") + } + err = ts.WriteChanges(ctx, db, tracer) + require.NoError(err, "Error writing changes.") + // Check if db was updated correctly + for _, key := range keys { + _, err := db.GetValue(ctx, key) + require.ErrorIs(err, database.ErrNotFound, "Value not removed from db.") + } +} func BenchmarkFetchAndSetScope(b *testing.B) { for _, size := range []int{100, 1000, 10000} { - b.Run(fmt.Sprintf("get_validator_set_%d_validators", size), func(b *testing.B) { - benchmarkGetValidatorSet(b, size) + b.Run(fmt.Sprintf("fetch_and_set_scope_%d_keys", size), func(b *testing.B) { + benchmarkFetchAndSetScope(b, size) }) } } -func benchmarkGetValidatorSet(b *testing.B, size int) { +func benchmarkFetchAndSetScope(b *testing.B, size int) { require := require.New(b) - ts := New(1000) + ts := New(10) db := NewTestDB() ctx := context.TODO() keys := [][]byte{} vals := [][]byte{} - k := randomBytes(65); - + // each k/v is unique to simulate worst case for range "0..size" { - keys = append(keys, k) + keys = append(keys, randomBytes(65)) vals = append(vals, randomBytes(8)) } From a5a957a5a462779b170042d67ef580e87cec74c2 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 4 May 2023 20:45:22 -0400 Subject: [PATCH 04/10] Minor refactor Signed-off-by: Sam Batschelet --- chain/processor.go | 17 ++++++++--------- tstate/tstate.go | 31 +++++++++++++++++-------------- tstate/tstate_test.go | 37 +++++++++++++++++++------------------ 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index 3824248838..511fee72cd 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -20,7 +20,7 @@ type fetchData struct { type txData struct { tx *Transaction - storage map[[65]byte][]byte + storage map[[tstate.MapKeyLength]byte][]byte } type Processor struct { @@ -49,27 +49,26 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := make(map[[65]byte]*fetchData, len(p.blk.GetTxs())) + alreadyFetched := make(map[[tstate.MapKeyLength]byte]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { storage := map[[65]byte][]byte{} for _, k := range tx.StateKeys(sm) { - var ary [65]byte - copy(ary[:], k) - if v, ok := alreadyFetched[ary]; ok { + sk := tstate.ToStateKeyArray(k) + if v, ok := alreadyFetched[sk]; ok { if v.exists { - storage[ary] = v.v + storage[sk] = v.v } continue } v, err := db.GetValue(ctx, k) if errors.Is(err, database.ErrNotFound) { - alreadyFetched[ary] = &fetchData{nil, false} + alreadyFetched[sk] = &fetchData{nil, false} continue } else if err != nil { panic(err) } - alreadyFetched[ary] = &fetchData{v, true} - storage[ary] = v + alreadyFetched[sk] = &fetchData{v, true} + storage[sk] = v } p.readyTxs <- &txData{tx, storage} } diff --git a/tstate/tstate.go b/tstate/tstate.go index 659c1a999f..f06b445bce 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -15,7 +15,7 @@ import ( ) type op struct { - k [65]byte + k [MapKeyLength]byte pastExists bool pastV []byte @@ -32,16 +32,18 @@ type cacheItem struct { Exists bool } +const MapKeyLength = 65 + // TState defines a struct for storing temporary state. type TState struct { - changedKeys map[[65]byte]*tempStorage - fetchCache map[[65]byte]*cacheItem // in case we evict and want to re-fetch + changedKeys map[[MapKeyLength]byte]*tempStorage + fetchCache map[[MapKeyLength]byte]*cacheItem // in case we evict and want to re-fetch // We don't differentiate between read and write scope because it is very // uncommon for a user to write something without first reading what is // there. scope [][]byte // stores a list of managed keys in the TState struct - scopeStorage map[[65]byte][]byte + scopeStorage map[[MapKeyLength]byte][]byte // Ops is a record of all operations performed on [TState]. Tracking // operations allows for reverting state to a certain point-in-time. @@ -52,9 +54,9 @@ type TState struct { // maps to have an initial size of [storageSize] and [changedSize] respectively. func New(changedSize int) *TState { return &TState{ - changedKeys: make(map[[65]byte]*tempStorage, changedSize), + changedKeys: make(map[[MapKeyLength]byte]*tempStorage, changedSize), - fetchCache: map[[65]byte]*cacheItem{}, + fetchCache: map[[MapKeyLength]byte]*cacheItem{}, ops: make([]*op, 0, changedSize), } @@ -67,7 +69,7 @@ func (ts *TState) GetValue(ctx context.Context, key []byte) ([]byte, error) { if !ts.checkScope(ctx, key) { return nil, ErrKeyNotSpecified } - v, _, exists := ts.getValue(ctx, newFixedSizeByteArray(key)) + v, _, exists := ts.getValue(ctx, ToStateKeyArray(key)) if !exists { return nil, database.ErrNotFound } @@ -92,10 +94,10 @@ func (ts *TState) getValue(_ context.Context, key [65]byte) ([]byte, bool, bool) // FetchAndSetScope then sets the scope of ts to [keys]. If a key exists in // ts.fetchCache set the key's value to the value from cache. func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Database) error { - ts.scopeStorage = map[[65]byte][]byte{} + ts.scopeStorage = map[[MapKeyLength]byte][]byte{} for _, key := range keys { - k := newFixedSizeByteArray(key) + k := ToStateKeyArray(key) if val, ok := ts.fetchCache[k]; ok { if val.Exists { ts.scopeStorage[k] = val.Value @@ -139,7 +141,7 @@ func (ts *TState) Insert(ctx context.Context, key []byte, value []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - k := newFixedSizeByteArray(key) + k := ToStateKeyArray(key) past, changed, exists := ts.getValue(ctx, k) ts.ops = append(ts.ops, &op{ k: k, @@ -151,13 +153,13 @@ func (ts *TState) Insert(ctx context.Context, key []byte, value []byte) error { return nil } -// Renove deletes a key-value pair from ts.storage. +// Remove deletes a key-value pair from ts.storage. func (ts *TState) Remove(ctx context.Context, key []byte) error { if !ts.checkScope(ctx, key) { return ErrKeyNotSpecified } - k := newFixedSizeByteArray(key) + k := ToStateKeyArray(key) past, changed, exists := ts.getValue(ctx, k) if !exists { return nil @@ -229,8 +231,9 @@ func (ts *TState) WriteChanges( return nil } -func newFixedSizeByteArray(key []byte) [65]byte { - var k [65]byte +// ToStateKeyArray converts a byte slice to byte array. +func ToStateKeyArray(key []byte) [MapKeyLength]byte { + var k [MapKeyLength]byte copy(k[:], key) return k } diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 70d468c8ac..01ffe16d3f 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/hypersdk/trace" + "github.com/stretchr/testify/require" ) @@ -54,7 +55,7 @@ func TestGetValue(t *testing.T) { _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{newFixedSizeByteArray(TestKey): TestVal}) + ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{ToStateKeyArray(TestKey): TestVal}) val, err := ts.GetValue(ctx, TestKey) require.NoError(err, "Error getting value.") require.Equal(TestVal, val, "Value was not saved correctly.") @@ -65,7 +66,7 @@ func TestGetValueNoStorage(t *testing.T) { ctx := context.TODO() ts := New(10) // SetScope but dont add to storage - ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(database.ErrNotFound, err, "No error thrown.") } @@ -78,7 +79,7 @@ func TestInsertNew(t *testing.T) { err := ts.Insert(ctx, TestKey, TestVal) require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) // Insert key err = ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error thrown.") @@ -93,7 +94,7 @@ func TestInsertUpdate(t *testing.T) { ctx := context.TODO() ts := New(10) // SetScope and add - ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{newFixedSizeByteArray(TestKey): TestVal}) + ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{ToStateKeyArray(TestKey): TestVal}) require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") // Insert key newVal := []byte("newVal") @@ -162,7 +163,7 @@ func TestSetScope(t *testing.T) { ts := New(10) ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) require.Equal(keys, ts.scope, "Scope not updated correctly.") } @@ -170,7 +171,7 @@ func TestRemoveInsertRollback(t *testing.T) { require := require.New(t) ts := New(10) ctx := context.TODO() - ts.SetScope(ctx, [][]byte{TestKey}, map[[65]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) // Insert err := ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error from insert.") @@ -218,7 +219,7 @@ func TestRestoreInsert(t *testing.T) { ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) require.NoError(err, "Error inserting.") @@ -248,10 +249,10 @@ func TestRestoreDelete(t *testing.T) { ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[65]byte][]byte{ - newFixedSizeByteArray(keys[0]): vals[0], - newFixedSizeByteArray(keys[1]): vals[1], - newFixedSizeByteArray(keys[2]): vals[2], + ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{ + ToStateKeyArray(keys[0]): vals[0], + ToStateKeyArray(keys[1]): vals[1], + ToStateKeyArray(keys[2]): vals[2], }) // Check scope for i, key := range keys { @@ -287,7 +288,7 @@ func TestWriteChanges(t *testing.T) { tracer, _ := trace.New(&trace.Config{Enabled: false}) keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[65]byte][]byte{}) + ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) // Add for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) @@ -305,10 +306,10 @@ func TestWriteChanges(t *testing.T) { } // Remove ts = New(10) - ts.SetScope(ctx, keys, map[[65]byte][]byte{ - newFixedSizeByteArray(keys[0]): vals[0], - newFixedSizeByteArray(keys[1]): vals[1], - newFixedSizeByteArray(keys[2]): vals[2], + ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{ + ToStateKeyArray(keys[0]): vals[0], + ToStateKeyArray(keys[1]): vals[1], + ToStateKeyArray(keys[2]): vals[2], }) for _, key := range keys { err := ts.Remove(ctx, key) @@ -326,7 +327,7 @@ func TestWriteChanges(t *testing.T) { } func BenchmarkFetchAndSetScope(b *testing.B) { - for _, size := range []int{100, 1000, 10000} { + for _, size := range []int{10, 100, 1000} { b.Run(fmt.Sprintf("fetch_and_set_scope_%d_keys", size), func(b *testing.B) { benchmarkFetchAndSetScope(b, size) }) @@ -344,7 +345,7 @@ func benchmarkFetchAndSetScope(b *testing.B, size int) { // each k/v is unique to simulate worst case for range "0..size" { - keys = append(keys, randomBytes(65)) + keys = append(keys, randomBytes(MapKeyLength)) vals = append(vals, randomBytes(8)) } From e6c8653ee27aeb3818986073159c32e4cf9117bf Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Thu, 4 May 2023 20:46:19 -0400 Subject: [PATCH 05/10] fmt Signed-off-by: Sam Batschelet --- tstate/tstate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 01ffe16d3f..7f653bec66 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/hypersdk/trace" - + "github.com/stretchr/testify/require" ) From 1d7c14632c987033b6f3472c602287623679a41b Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 5 May 2023 08:40:59 -0400 Subject: [PATCH 06/10] Updates from feedback Signed-off-by: Sam Batschelet --- tstate/tstate.go | 24 +++++++++++++----------- tstate/tstate_test.go | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/tstate/tstate.go b/tstate/tstate.go index f06b445bce..abee9f74d7 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -15,7 +15,7 @@ import ( ) type op struct { - k [MapKeyLength]byte + k Key pastExists bool pastV []byte @@ -34,16 +34,18 @@ type cacheItem struct { const MapKeyLength = 65 +type Key [MapKeyLength]byte + // TState defines a struct for storing temporary state. type TState struct { - changedKeys map[[MapKeyLength]byte]*tempStorage - fetchCache map[[MapKeyLength]byte]*cacheItem // in case we evict and want to re-fetch + changedKeys map[Key]*tempStorage + fetchCache map[Key]*cacheItem // in case we evict and want to re-fetch // We don't differentiate between read and write scope because it is very // uncommon for a user to write something without first reading what is // there. scope [][]byte // stores a list of managed keys in the TState struct - scopeStorage map[[MapKeyLength]byte][]byte + scopeStorage map[Key][]byte // Ops is a record of all operations performed on [TState]. Tracking // operations allows for reverting state to a certain point-in-time. @@ -54,9 +56,9 @@ type TState struct { // maps to have an initial size of [storageSize] and [changedSize] respectively. func New(changedSize int) *TState { return &TState{ - changedKeys: make(map[[MapKeyLength]byte]*tempStorage, changedSize), + changedKeys: make(map[Key]*tempStorage, changedSize), - fetchCache: map[[MapKeyLength]byte]*cacheItem{}, + fetchCache: map[Key]*cacheItem{}, ops: make([]*op, 0, changedSize), } @@ -76,7 +78,7 @@ func (ts *TState) GetValue(ctx context.Context, key []byte) ([]byte, error) { return v, nil } -func (ts *TState) getValue(_ context.Context, key [65]byte) ([]byte, bool, bool) { +func (ts *TState) getValue(_ context.Context, key Key) ([]byte, bool, bool) { if v, ok := ts.changedKeys[key]; ok { if v.removed { return nil, true, false @@ -94,7 +96,7 @@ func (ts *TState) getValue(_ context.Context, key [65]byte) ([]byte, bool, bool) // FetchAndSetScope then sets the scope of ts to [keys]. If a key exists in // ts.fetchCache set the key's value to the value from cache. func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Database) error { - ts.scopeStorage = map[[MapKeyLength]byte][]byte{} + ts.scopeStorage = map[Key][]byte{} for _, key := range keys { k := ToStateKeyArray(key) @@ -120,7 +122,7 @@ func (ts *TState) FetchAndSetScope(ctx context.Context, keys [][]byte, db Databa } // SetReadScope sets the readscope of ts to [keys]. -func (ts *TState) SetScope(_ context.Context, keys [][]byte, storage map[[65]byte][]byte) { +func (ts *TState) SetScope(_ context.Context, keys [][]byte, storage map[Key][]byte) { ts.scope = keys ts.scopeStorage = storage } @@ -232,8 +234,8 @@ func (ts *TState) WriteChanges( } // ToStateKeyArray converts a byte slice to byte array. -func ToStateKeyArray(key []byte) [MapKeyLength]byte { - var k [MapKeyLength]byte +func ToStateKeyArray(key []byte) Key { + var k Key copy(k[:], key) return k } diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 7f653bec66..d87db9d9ec 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -55,7 +55,7 @@ func TestGetValue(t *testing.T) { _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(err, ErrKeyNotSpecified, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{ToStateKeyArray(TestKey): TestVal}) + ts.SetScope(ctx, [][]byte{TestKey}, map[Key][]byte{ToStateKeyArray(TestKey): TestVal}) val, err := ts.GetValue(ctx, TestKey) require.NoError(err, "Error getting value.") require.Equal(TestVal, val, "Value was not saved correctly.") @@ -66,7 +66,7 @@ func TestGetValueNoStorage(t *testing.T) { ctx := context.TODO() ts := New(10) // SetScope but dont add to storage - ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[Key][]byte{}) _, err := ts.GetValue(ctx, TestKey) require.ErrorIs(database.ErrNotFound, err, "No error thrown.") } @@ -79,7 +79,7 @@ func TestInsertNew(t *testing.T) { err := ts.Insert(ctx, TestKey, TestVal) require.ErrorIs(ErrKeyNotSpecified, err, "No error thrown.") // SetScope - ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[Key][]byte{}) // Insert key err = ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error thrown.") @@ -94,7 +94,7 @@ func TestInsertUpdate(t *testing.T) { ctx := context.TODO() ts := New(10) // SetScope and add - ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{ToStateKeyArray(TestKey): TestVal}) + ts.SetScope(ctx, [][]byte{TestKey}, map[Key][]byte{ToStateKeyArray(TestKey): TestVal}) require.Equal(0, ts.OpIndex(), "SetStorage operation was not added.") // Insert key newVal := []byte("newVal") @@ -163,7 +163,7 @@ func TestSetScope(t *testing.T) { ts := New(10) ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} - ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, keys, map[Key][]byte{}) require.Equal(keys, ts.scope, "Scope not updated correctly.") } @@ -171,7 +171,7 @@ func TestRemoveInsertRollback(t *testing.T) { require := require.New(t) ts := New(10) ctx := context.TODO() - ts.SetScope(ctx, [][]byte{TestKey}, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, [][]byte{TestKey}, map[Key][]byte{}) // Insert err := ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error from insert.") @@ -219,7 +219,7 @@ func TestRestoreInsert(t *testing.T) { ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, keys, map[Key][]byte{}) for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) require.NoError(err, "Error inserting.") @@ -249,7 +249,7 @@ func TestRestoreDelete(t *testing.T) { ctx := context.TODO() keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{ + ts.SetScope(ctx, keys, map[Key][]byte{ ToStateKeyArray(keys[0]): vals[0], ToStateKeyArray(keys[1]): vals[1], ToStateKeyArray(keys[2]): vals[2], @@ -288,7 +288,7 @@ func TestWriteChanges(t *testing.T) { tracer, _ := trace.New(&trace.Config{Enabled: false}) keys := [][]byte{[]byte("key1"), []byte("key2"), []byte("key3")} vals := [][]byte{[]byte("val1"), []byte("val2"), []byte("val3")} - ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{}) + ts.SetScope(ctx, keys, map[Key][]byte{}) // Add for i, key := range keys { err := ts.Insert(ctx, key, vals[i]) @@ -306,7 +306,7 @@ func TestWriteChanges(t *testing.T) { } // Remove ts = New(10) - ts.SetScope(ctx, keys, map[[MapKeyLength]byte][]byte{ + ts.SetScope(ctx, keys, map[Key][]byte{ ToStateKeyArray(keys[0]): vals[0], ToStateKeyArray(keys[1]): vals[1], ToStateKeyArray(keys[2]): vals[2], @@ -344,7 +344,7 @@ func benchmarkFetchAndSetScope(b *testing.B, size int) { vals := [][]byte{} // each k/v is unique to simulate worst case - for range "0..size" { + for i := 0; i <= size; i++ { keys = append(keys, randomBytes(MapKeyLength)) vals = append(vals, randomBytes(8)) } From c688a54cc136b4510141cce8496af8c120d51616 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 5 May 2023 10:15:02 -0400 Subject: [PATCH 07/10] nits Signed-off-by: Sam Batschelet --- chain/processor.go | 6 +++--- tstate/tstate.go | 5 +++-- tstate/tstate_test.go | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index 511fee72cd..a467c6e68b 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -20,7 +20,7 @@ type fetchData struct { type txData struct { tx *Transaction - storage map[[tstate.MapKeyLength]byte][]byte + storage map[tstate.Key][]byte } type Processor struct { @@ -49,9 +49,9 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := make(map[[tstate.MapKeyLength]byte]*fetchData, len(p.blk.GetTxs())) + alreadyFetched := make(map[tstate.Key]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { - storage := map[[65]byte][]byte{} + storage := map[tstate.Key][]byte{} for _, k := range tx.StateKeys(sm) { sk := tstate.ToStateKeyArray(k) if v, ok := alreadyFetched[sk]; ok { diff --git a/tstate/tstate.go b/tstate/tstate.go index abee9f74d7..47e3769a7f 100644 --- a/tstate/tstate.go +++ b/tstate/tstate.go @@ -8,10 +8,11 @@ import ( "context" "errors" - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/trace" "go.opentelemetry.io/otel/attribute" oteltrace "go.opentelemetry.io/otel/trace" + + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/trace" ) type op struct { diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index d87db9d9ec..1bc55e107b 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -122,7 +122,7 @@ func TestFetchAndSetScope(t *testing.T) { } err := ts.FetchAndSetScope(ctx, keys, db) require.NoError(err, "Error thrown.") - require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(0, ts.OpIndex(), "Operations not updated correctly.") require.Equal(keys, ts.scope, "Scope not updated correctly.") // Check values for i, key := range keys { @@ -146,7 +146,7 @@ func TestFetchAndSetScopeMissingKey(t *testing.T) { } err := ts.FetchAndSetScope(ctx, keys, db) require.NoError(err, "Error thrown.") - require.Equal(0, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(0, ts.OpIndex(), "Operations not updated correctly.") require.Equal(keys, ts.scope, "Scope not updated correctly.") // Check values for i, key := range keys[:len(keys)-1] { @@ -178,20 +178,20 @@ func TestRemoveInsertRollback(t *testing.T) { v, err := ts.GetValue(ctx, TestKey) require.NoError(err) require.Equal(TestVal, v) - require.Equal(1, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(1, ts.OpIndex(), "Operations not updated correctly.") // Remove err = ts.Remove(ctx, TestKey) require.NoError(err, "Error from remove.") _, err = ts.GetValue(ctx, TestKey) require.ErrorIs(err, database.ErrNotFound, "Key not deleted from storage.") - require.Equal(2, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(2, ts.OpIndex(), "Operations not updated correctly.") // Insert err = ts.Insert(ctx, TestKey, TestVal) require.NoError(err, "Error from insert.") v, err = ts.GetValue(ctx, TestKey) require.NoError(err) require.Equal(TestVal, v) - require.Equal(3, ts.OpIndex(), "Opertions not updated correctly.") + require.Equal(3, ts.OpIndex(), "Operations not updated correctly.") require.Equal(1, ts.PendingChanges()) // Rollback ts.Rollback(ctx, 2) @@ -327,7 +327,7 @@ func TestWriteChanges(t *testing.T) { } func BenchmarkFetchAndSetScope(b *testing.B) { - for _, size := range []int{10, 100, 1000} { + for _, size := range []int{4, 8, 16, 32, 64, 128} { b.Run(fmt.Sprintf("fetch_and_set_scope_%d_keys", size), func(b *testing.B) { benchmarkFetchAndSetScope(b, size) }) From 25e5f28c7555eda9130a1f1abeaf41a86f5f146f Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 5 May 2023 15:50:48 -0400 Subject: [PATCH 08/10] wip Signed-off-by: Sam Batschelet --- chain/processor.go | 6 ++--- tstate/tstate_test.go | 57 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index a467c6e68b..8a8d122210 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -20,7 +20,7 @@ type fetchData struct { type txData struct { tx *Transaction - storage map[tstate.Key][]byte + storage map[*tstate.Key][]byte } type Processor struct { @@ -49,9 +49,9 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := make(map[tstate.Key]*fetchData, len(p.blk.GetTxs())) + alreadyFetched := make(map[*tstate.Key]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { - storage := map[tstate.Key][]byte{} + storage := map[*tstate.Key][]byte{} for _, k := range tx.StateKeys(sm) { sk := tstate.ToStateKeyArray(k) if v, ok := alreadyFetched[sk]; ok { diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index 1bc55e107b..a8423c922b 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -334,21 +334,21 @@ func BenchmarkFetchAndSetScope(b *testing.B) { } } +func BenchmarkInsert(b *testing.B) { + for _, size := range []int{4, 8, 16, 32, 64, 128} { + b.Run(fmt.Sprintf("insert_%d_keys", size), func(b *testing.B) { + benchmarkInsert(b, size) + }) + } +} + func benchmarkFetchAndSetScope(b *testing.B, size int) { require := require.New(b) - ts := New(10) + ts := New(size) db := NewTestDB() ctx := context.TODO() - keys := [][]byte{} - vals := [][]byte{} - - // each k/v is unique to simulate worst case - for i := 0; i <= size; i++ { - keys = append(keys, randomBytes(MapKeyLength)) - vals = append(vals, randomBytes(8)) - } - + keys, vals := initializeSet(size) for i, key := range keys { err := db.Insert(ctx, key, vals[i]) require.NoError(err, "Error during insert.") @@ -363,6 +363,43 @@ func benchmarkFetchAndSetScope(b *testing.B, size int) { b.StopTimer() } +func benchmarkInsert(b *testing.B, size int) { + require := require.New(b) + ts := New(size) + ctx := context.TODO() + + keys, vals := initializeSet(size) + + storage := map[Key][]byte{} + for i, key := range keys { + storage[ToStateKeyArray(key)] = vals[i] + } + + ts.SetScope(ctx, keys, storage) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for i, key := range keys { + err := ts.Insert(ctx, key, vals[i]) + require.NoError(err, "Error during insert.") + } + } + b.ReportAllocs() + b.StopTimer() +} + +func initializeSet(size int) ([][]byte, [][]byte) { + keys := [][]byte{} + vals := [][]byte{} + + for i := 0; i <= size; i++ { + keys = append(keys, randomBytes(33)) + vals = append(vals, randomBytes(8)) + } + + return keys, vals +} + func randomBytes(size int) []byte { bytes := make([]byte, size) rand.Read(bytes) From d8abb9286536cd3612da593a81b3cec5b7376c2a Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 5 May 2023 16:09:09 -0400 Subject: [PATCH 09/10] Add get value benchmark Signed-off-by: Sam Batschelet --- tstate/tstate_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tstate/tstate_test.go b/tstate/tstate_test.go index a8423c922b..234aff1c8f 100644 --- a/tstate/tstate_test.go +++ b/tstate/tstate_test.go @@ -342,6 +342,15 @@ func BenchmarkInsert(b *testing.B) { } } + +func BenchmarkGetValue(b *testing.B) { + for _, size := range []int{4, 8, 16, 32, 64, 128} { + b.Run(fmt.Sprintf("get_%d_keys", size), func(b *testing.B) { + benchmarkGetValue(b, size) + }) + } +} + func benchmarkFetchAndSetScope(b *testing.B, size int) { require := require.New(b) ts := New(size) @@ -388,6 +397,31 @@ func benchmarkInsert(b *testing.B, size int) { b.StopTimer() } +func benchmarkGetValue(b *testing.B, size int) { + require := require.New(b) + ts := New(size) + ctx := context.TODO() + + keys, vals := initializeSet(size) + + storage := map[Key][]byte{} + for i, key := range keys { + storage[ToStateKeyArray(key)] = vals[i] + } + + ts.SetScope(ctx, keys, storage) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, key := range keys { + _, err := ts.GetValue(ctx, key) + require.NoError(err, "Error during insert.") + } + } + b.ReportAllocs() + b.StopTimer() +} + func initializeSet(size int) ([][]byte, [][]byte) { keys := [][]byte{} vals := [][]byte{} From e6d1a05d59738c2cdaffb1a097356ad8685e6063 Mon Sep 17 00:00:00 2001 From: Sam Batschelet Date: Fri, 5 May 2023 16:20:28 -0400 Subject: [PATCH 10/10] nits Signed-off-by: Sam Batschelet --- chain/processor.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/processor.go b/chain/processor.go index 8a8d122210..a467c6e68b 100644 --- a/chain/processor.go +++ b/chain/processor.go @@ -20,7 +20,7 @@ type fetchData struct { type txData struct { tx *Transaction - storage map[*tstate.Key][]byte + storage map[tstate.Key][]byte } type Processor struct { @@ -49,9 +49,9 @@ func (p *Processor) Prefetch(ctx context.Context, db Database) { defer span.End() // Store required keys for each set - alreadyFetched := make(map[*tstate.Key]*fetchData, len(p.blk.GetTxs())) + alreadyFetched := make(map[tstate.Key]*fetchData, len(p.blk.GetTxs())) for _, tx := range p.blk.GetTxs() { - storage := map[*tstate.Key][]byte{} + storage := map[tstate.Key][]byte{} for _, k := range tx.StateKeys(sm) { sk := tstate.ToStateKeyArray(k) if v, ok := alreadyFetched[sk]; ok {