Skip to content

Commit

Permalink
Store non-hash values in a BPT [#3513]
Browse files Browse the repository at this point in the history
  • Loading branch information
firelizzard18 committed Jun 18, 2024
1 parent 27f3572 commit fa3f232
Show file tree
Hide file tree
Showing 22 changed files with 281 additions and 104 deletions.
4 changes: 2 additions & 2 deletions internal/database/bpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ func (b *Batch) IterateAccounts() *AccountIterator {
}

func (b *Batch) ForEachAccount(fn func(account *Account, hash [32]byte) error) error {
return bpt.ForEach(b.BPT(), func(key *record.Key, hash [32]byte) error {
return bpt.ForEach(b.BPT(), func(key *record.Key, hash []byte) error {
// Create an Account object
u, err := b.getAccountUrl(key)
if err != nil {
return errors.UnknownError.Wrap(err)
}

return fn(b.Account(u), hash)
return fn(b.Account(u), *(*[32]byte)(hash))
})
}

Expand Down
3 changes: 1 addition & 2 deletions internal/database/bpt_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ func (a *Account) putBpt() error {
return err
}

hash := *(*[32]byte)(hasher.MerkleHash())
return a.parent.BPT().Insert(a.key, hash)
return a.parent.BPT().Insert(a.key, hasher.MerkleHash())
}

// BptReceipt builds a BPT receipt for the account.
Expand Down
6 changes: 3 additions & 3 deletions internal/database/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (e *eventsSet[T]) postRestore() error {
for _, v := range v {
err = e.bpt.Insert(
e.baseKey.AppendKey(e.getKey(v)),
*(*[32]byte)(e.getHash(v)))
e.getHash(v))
if err != nil {
return errors.UnknownError.Wrap(err)
}
Expand Down Expand Up @@ -204,7 +204,7 @@ func (e *eventsSet[T]) Put(v []T) error {
kh := k.Hash()
err = e.bpt.Insert(
e.baseKey.AppendKey(k),
*(*[32]byte)(e.getHash(v)))
e.getHash(v))
if err != nil {
return errors.UnknownError.Wrap(err)
}
Expand All @@ -227,7 +227,7 @@ func (e *eventsSet[T]) Add(v ...T) error {
for _, v := range v {
err := e.bpt.Insert(
e.baseKey.AppendKey(e.getKey(v)),
*(*[32]byte)(e.getHash(v)))
e.getHash(v))
if err != nil {
return errors.UnknownError.Wrap(err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/accumulate/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func TestCreateCheckpoint(t *testing.T) {
break
}
require.NoError(t, err)
require.NoError(t, temp.BPT().Insert(rec.Key, *(*[32]byte)(rec.Value)))
require.NoError(t, temp.BPT().Insert(rec.Key, rec.Value))
}

case snapshot.SectionTypeRecords:
Expand Down
4 changes: 2 additions & 2 deletions pkg/database/bpt/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func BenchmarkInsert(b *testing.B) {

var rh common.RandHash
for i := 0; i < b.N; i++ {
err := bpt.Insert(record.KeyFromHash(rh.NextA()), rh.NextA())
err := bpt.Insert(record.KeyFromHash(rh.NextA()), rh.Next())
if err != nil {
b.Fatal(err)
}
Expand All @@ -41,7 +41,7 @@ func BenchmarkInsertSubbatch(b *testing.B) {
var rh common.RandHash
for i := 0; i < b.N; i++ {
sub := model.Begin()
err := sub.BPT().Insert(record.KeyFromHash(rh.NextA()), rh.NextA())
err := sub.BPT().Insert(record.KeyFromHash(rh.NextA()), rh.Next())
if err != nil {
b.Fatal(err)
}
Expand Down
33 changes: 28 additions & 5 deletions pkg/database/bpt/bpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

type KeyValuePair struct {
Key *record.Key
Value [32]byte
Value []byte
}

// New returns a new BPT.
Expand Down Expand Up @@ -83,6 +83,29 @@ func (p paramsRecord) Get() (*parameters, error) {
return v, nil
}

func (p paramsRecord) Put(v *parameters) error {
old, err := p.Value.Get()
switch {
case errors.Is(err, errors.NotFound):
// Parameters have not been set so they can be set to anything
return p.Value.Put(v)

case err != nil:
return errors.UnknownError.Wrap(err)
}

// Create copies with the transient values cleared
a, b := old.Copy(), v.Copy()
a.RootHash, a.MaxHeight = [32]byte{}, 0
b.RootHash, b.MaxHeight = [32]byte{}, 0

if !a.Equal(b) {
return errors.BadRequest.With("cannot change BPT parameters once set")
}

return p.Value.Put(v)
}

// nodeKeyAt
// We need a key to address nodes in the protocol. These nodes need a unique key
// for debugging purposes.
Expand Down Expand Up @@ -142,19 +165,19 @@ func (b *BPT) newRoot() *rootRecord {
}

// Get retrieves the latest hash associated with the given key.
func (b *BPT) Get(key *record.Key) ([32]byte, error) {
func (b *BPT) Get(key *record.Key) ([]byte, error) {
if v, ok := b.pending[key.Hash()]; ok {
if v.delete {
return [32]byte{}, errors.NotFound
return nil, errors.NotFound
}
return v.value, nil
}

e, err := b.getRoot().getLeaf(key.Hash())
if err != nil {
return [32]byte{}, errors.UnknownError.Wrap(err)
return nil, errors.UnknownError.Wrap(err)
}
return e.Hash, nil
return e.Value, nil
}

// getLeaf walks the tree and returns the leaf node for the given key.
Expand Down
4 changes: 2 additions & 2 deletions pkg/database/bpt/bpt_receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ func (b *BPT) GetReceipt(key *record.Key) (*merkle.Receipt, error) {

// Walk up the tree
receipt := new(merkle.Receipt)
receipt.Start = n.Hash[:]
working := n.Hash
receipt.Start = n.Value
working, _ := n.getHash()
var br *branch
for n := node(n); ; n = br {
// Get the parent
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/bpt/bpt_receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestBPT_receipt(t *testing.T) {
values.SetSeed([]byte{1, 2, 3}) // use a different sequence for values
for i := 0; i < numberEntries; i++ { // For the number of Entries specified for the BPT
chainID := keys.NextAList() // Get a key, keep a list
value := values.NextA() // Get some value (don't really care what it is)
value := values.Next() // Get some value (don't really care what it is)
err := bpt.Insert(record.KeyFromHash(chainID), value) // Insert the Key with the value into the BPT
require.NoError(t, err)
}
Expand Down
16 changes: 12 additions & 4 deletions pkg/database/bpt/bpt_savestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package bpt

import (
"crypto/sha256"
"io"
"os"

Expand Down Expand Up @@ -67,12 +68,19 @@ func SaveSnapshotV1(b *BPT, file io.WriteSeeker, loadState func(key storage.Key,
NodeCnt += uint64(len(bptVals))

for _, v := range bptVals { // For all the key values we got (as many as 1000)
kh := v.Key.Hash() //
kh := v.Key.Hash()
var vh [32]byte
if len(v.Value) == 32 {
copy(vh[:], v.Value)
} else {
vh = sha256.Sum256(v.Value)
}

_, e1 := file.Write(kh[:]) // Write the key out
_, e2 := file.Write(v.Value[:]) // Write the hash out
_, e2 := file.Write(vh[:]) // Write the hash out
_, e3 := file.Write(common.Uint64FixedBytes(vOffset)) // And the current offset to the next value

value, e4 := loadState(kh, v.Value) // Get that next value
value, e4 := loadState(kh, vh) // Get that next value
vLen := uint64(len(value)) // get the value's length as uint64
_, e5 := values.Write(common.Uint64FixedBytes(vLen)) // Write out the length
_, e6 := values.Write(value) // write out the value
Expand Down Expand Up @@ -130,7 +138,7 @@ func LoadSnapshotV1(b *BPT, file ioutil2.SectionReader, storeState func(key stor
}

return ReadSnapshotV1(file, func(key storage.Key, hash [32]byte, reader ioutil2.SectionReader) error {
err := b.Insert(record.KeyFromHash(key), hash) // Insert the key/hash into the BPT
err := b.Insert(record.KeyFromHash(key), hash[:]) // Insert the key/hash into the BPT
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/bpt/bpt_savestate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestSaveState(t *testing.T) {
hash := sha256.Sum256(value)
err := storeTx.Put(record.KeyFromHash(hash), value)
require.NoError(t, err)
err = bpt.Insert(record.KeyFromHash(chainID), hash) // Insert the Key with the value into the BPT
err = bpt.Insert(record.KeyFromHash(chainID), hash[:]) // Insert the Key with the value into the BPT
require.NoError(t, err)
}
require.NoError(t, bpt.Commit())
Expand Down
8 changes: 8 additions & 0 deletions pkg/database/bpt/enums.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ nodeType:
description: is a leaf node with an expanded key
label: leaf+key
value: 5
LeafWithValue:
description: is a leaf node with a non-hash value
label: leaf+value
value: 6
LeafWithExpandedKeyAndValue:
description: is a leaf node with an expanded key and non-hash value
label: leaf+key+value
value: 7
22 changes: 20 additions & 2 deletions pkg/database/bpt/enums_gen.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 The Accumulate Authors
// Copyright 2024 The Accumulate Authors
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
Expand Down Expand Up @@ -29,14 +29,20 @@ const nodeTypeBoundary nodeType = 4
// nodeTypeLeafWithExpandedKey is a leaf node with an expanded key.
const nodeTypeLeafWithExpandedKey nodeType = 5

// nodeTypeLeafWithValue is a leaf node with a non-hash value.
const nodeTypeLeafWithValue nodeType = 6

// nodeTypeLeafWithExpandedKeyAndValue is a leaf node with an expanded key and non-hash value.
const nodeTypeLeafWithExpandedKeyAndValue nodeType = 7

// GetEnumValue returns the value of the node Type
func (v nodeType) GetEnumValue() uint64 { return uint64(v) }

// SetEnumValue sets the value. SetEnumValue returns false if the value is invalid.
func (v *nodeType) SetEnumValue(id uint64) bool {
u := nodeType(id)
switch u {
case nodeTypeEmpty, nodeTypeBranch, nodeTypeLeaf, nodeTypeBoundary, nodeTypeLeafWithExpandedKey:
case nodeTypeEmpty, nodeTypeBranch, nodeTypeLeaf, nodeTypeBoundary, nodeTypeLeafWithExpandedKey, nodeTypeLeafWithValue, nodeTypeLeafWithExpandedKeyAndValue:
*v = u
return true
}
Expand All @@ -56,6 +62,10 @@ func (v nodeType) String() string {
return "boundary"
case nodeTypeLeafWithExpandedKey:
return "leaf+key"
case nodeTypeLeafWithValue:
return "leaf+value"
case nodeTypeLeafWithExpandedKeyAndValue:
return "leaf+key+value"
}
return fmt.Sprintf("nodeType:%d", v)
}
Expand All @@ -75,6 +85,14 @@ func nodeTypeByName(name string) (nodeType, bool) {
return nodeTypeLeafWithExpandedKey, true
case "leaf+key":
return nodeTypeLeafWithExpandedKey, true
case "leafwithvalue":
return nodeTypeLeafWithValue, true
case "leaf+value":
return nodeTypeLeafWithValue, true
case "leafwithexpandedkeyandvalue":
return nodeTypeLeafWithExpandedKeyAndValue, true
case "leaf+key+value":
return nodeTypeLeafWithExpandedKeyAndValue, true
}
return 0, false
}
Expand Down
22 changes: 11 additions & 11 deletions pkg/database/bpt/iterate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// ForEach calls the callback for each BPT entry.
func ForEach(b *BPT, fn func(key *record.Key, hash [32]byte) error) error {
func ForEach(b *BPT, fn func(key *record.Key, hash []byte) error) error {
it := b.Iterate(1000)
for it.Next() {
for _, v := range it.Value() {
Expand Down Expand Up @@ -82,19 +82,19 @@ func (it *Iterator) Err() error { return it.err }
// walkNode processes the entry (which is both left and right, but the same logic)
func walkNode(n node, found *bool, key [32]byte, values []KeyValuePair, pos *int) error {
switch n := n.(type) {
case *emptyNode: // If we find a nil, we have found our starting
*found = true // point. But we have nothing to do.
case *emptyNode: // If we find a nil, we have found our starting
*found = true // point. But we have nothing to do.

case *branch: // If a node, recurse and get its stuff.
case *branch: // If a node, recurse and get its stuff.
return n.walkRange(found, key, values, pos)

case *leaf: // If not a node, not nil, it is a value.
*found = true // No matter what, start collecting the range
if n.Key.Hash() == key { // But don't collect if equal to the key
break // because we use the last key to get the
} // next range
values[*pos] = KeyValuePair{Key: n.Key, Value: n.Hash} // Otherwise copy the value out of the BPT
*pos++ // and stuff it in the values list
case *leaf: // If not a node, not nil, it is a value.
*found = true // No matter what, start collecting the range
if n.Key.Hash() == key { // But don't collect if equal to the key
break // because we use the last key to get the
} // next range
values[*pos] = KeyValuePair{Key: n.Key, Value: n.Value} // Otherwise copy the value out of the BPT
*pos++ // and stuff it in the values list
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/database/bpt/iterate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func GetRangeFor(t *testing.T, numberEntries, rangeNum int) {
values.SetSeed([]byte{1, 2, 3}) // use a different sequence for values
for i := 0; i < numberEntries; i++ { // For the number of Entries specified for the BPT
chainID := keys.NextAList() // Get a key, keep a list
value := values.NextA() // Get some value (don't really care what it is)
value := values.Next() // Get some value (don't really care what it is)
err := bpt.Insert(record.KeyFromHash(chainID), value) // Insert the Key with the value into the BPT
require.NoError(t, err)
}
Expand Down
11 changes: 7 additions & 4 deletions pkg/database/bpt/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestOldBptStillWorks(t *testing.T) {
for _, entry := range testEntries {
value, err := model.BPT().Get(entry.Key)
require.NoError(t, err)
require.Equal(t, entry.Hash, value)
require.Equal(t, entry.Hash[:], value)
}

testEntryMap := map[[32]byte][32]byte{}
Expand All @@ -70,7 +70,8 @@ func TestOldBptStillWorks(t *testing.T) {

for it := model.BPT().Iterate(100); it.Next(); {
for _, leaf := range it.Value() {
require.Equal(t, testEntryMap[leaf.Key.Hash()], leaf.Value)
h := testEntryMap[leaf.Key.Hash()]
require.Equal(t, h[:], leaf.Value)
}
}
}
Expand All @@ -91,15 +92,17 @@ func TestSwitchKeyType(t *testing.T) {

// Insert entries using compressed keys
for _, e := range entries {
require.NoError(t, bpt.Insert(record.KeyFromHash(e.Hash()), e.Hash()))
h := e.Hash()
require.NoError(t, bpt.Insert(record.KeyFromHash(e.Hash()), h[:]))
}
require.NoError(t, bpt.Commit())
before := allBptKeys(bpt)

// Reload the tree and update using expanded keys
bpt = newBPT(nil, nil, store, nil, "BPT")
for _, e := range entries[:len(entries)/2] {
require.NoError(t, bpt.Insert(e, e.Append("foo").Hash()))
h := e.Append("foo").Hash()
require.NoError(t, bpt.Insert(e, h[:]))
}
require.NoError(t, bpt.Commit())

Expand Down
Loading

0 comments on commit fa3f232

Please sign in to comment.