Skip to content

Use a Value go type to consolidate #920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions ffi/firewood.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (db *Database) Batch(ops []KeyValue) ([]byte, error) {
C.size_t(len(ffiOps)),
unsafe.SliceData(ffiOps), // implicitly pinned
)
return extractBytesThenFree(&hash)
return (&Value{V: &hash}).intoBytes()
}

func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) {
Expand All @@ -159,12 +159,12 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) {
value: values.from(vals[i]),
}
}
idOrErr := C.fwd_propose_on_db(
val := C.fwd_propose_on_db(
db.handle,
C.size_t(len(ffiOps)),
unsafe.SliceData(ffiOps), // implicitly pinned
)
id, err := extractUintThenFree(&idOrErr)
bytes, id, err := (&Value{V: &val}).intoHashAndID()
if err != nil {
return nil, err
}
Expand All @@ -173,6 +173,7 @@ func (db *Database) Propose(keys, vals [][]byte) (*Proposal, error) {
return &Proposal{
handle: db.handle,
id: id,
root: bytes,
}, nil
}

Expand All @@ -186,7 +187,7 @@ func (db *Database) Get(key []byte) ([]byte, error) {
values, cleanup := newValueFactory()
defer cleanup()
val := C.fwd_get_latest(db.handle, values.from(key))
bytes, err := extractBytesThenFree(&val)
bytes, err := (&Value{V: &val}).intoBytes()

// If the root hash is not found, return nil.
if err != nil && strings.Contains(err.Error(), rootHashNotFound) {
Expand All @@ -203,7 +204,7 @@ func (db *Database) Root() ([]byte, error) {
return nil, errDBClosed
}
hash := C.fwd_root_hash(db.handle)
bytes, err := extractBytesThenFree(&hash)
bytes, err := (&Value{V: &hash}).intoBytes()

// If the root hash is not found, return a zeroed slice.
if err != nil && strings.Contains(err.Error(), rootHashNotFound) {
Expand Down
10 changes: 6 additions & 4 deletions ffi/firewood.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,9 @@ const struct DatabaseHandle *fwd_open_db(struct CreateOrOpenArgs args);
*
* # Returns
*
* A `Value` containing {id, null} if creating the proposal succeeded.
* A `Value` containing {0, "error message"} if creating the proposal failed.
* On success, a `Value` containing {len=id, data=hash}. In this case, the
* hash will always be 32 bytes, and the id will be non-zero.
* On failure, a `Value` containing {0, "error message"}.
*
* # Safety
*
Expand All @@ -318,8 +319,9 @@ struct Value fwd_propose_on_db(const struct DatabaseHandle *db,
*
* # Returns
*
* A `Value` containing {id, nil} if creating the proposal succeeded.
* A `Value` containing {0, "error message"} if creating the proposal failed.
* On success, a `Value` containing {len=id, data=hash}. In this case, the
* hash will always be 32 bytes, and the id will be non-zero.
* On failure, a `Value` containing {0, "error message"}.
*
* # Safety
*
Expand Down
67 changes: 48 additions & 19 deletions ffi/firewood_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,19 @@ func kvForTest(i int) KeyValue {
// 1. By calling [Database.Propose] and then [Proposal.Commit].
// 2. By calling [Database.Update] directly - no proposal storage is needed.
func TestInsert100(t *testing.T) {
type dbView interface {
Get(key []byte) ([]byte, error)
Propose(keys, vals [][]byte) (*Proposal, error)
Root() ([]byte, error)
}

tests := []struct {
name string
insert func(*Database, [][]byte, [][]byte) (root []byte, _ error)
insert func(dbView, [][]byte, [][]byte) (dbView, error)
}{
{
name: "Propose",
insert: func(db *Database, keys, vals [][]byte) ([]byte, error) {
name: "Propose and Commit",
insert: func(db dbView, keys, vals [][]byte) (dbView, error) {
proposal, err := db.Propose(keys, vals)
if err != nil {
return nil, err
Expand All @@ -219,13 +225,28 @@ func TestInsert100(t *testing.T) {
if err != nil {
return nil, err
}
return db.Root()
return db, nil
},
},
{
name: "Update",
insert: func(db *Database, keys, vals [][]byte) ([]byte, error) {
return db.Update(keys, vals)
insert: func(db dbView, keys, vals [][]byte) (dbView, error) {
actualDB, ok := db.(*Database)
if !ok {
return nil, fmt.Errorf("expected *Database, got %T", db)
}
_, err := actualDB.Update(keys, vals)
return db, err
},
},
{
name: "Propose",
insert: func(db dbView, keys, vals [][]byte) (dbView, error) {
proposal, err := db.Propose(keys, vals)
if err != nil {
return nil, err
}
return proposal, nil
},
},
}
Expand All @@ -240,20 +261,23 @@ func TestInsert100(t *testing.T) {
keys[i] = keyForTest(i)
vals[i] = valForTest(i)
}
rootFromInsert, err := tt.insert(db, keys, vals)
newDB, err := tt.insert(db, keys, vals)
require.NoError(t, err, "inserting")

for i := range keys {
got, err := db.Get(keys[i])
got, err := newDB.Get(keys[i])
require.NoErrorf(t, err, "%T.Get(%q)", db, keys[i])
// Cast as strings to improve debug messages.
want := string(vals[i])
require.Equal(t, want, string(got), "Recover nth batch-inserted value")
}

hash, err := db.Root()
hash, err := newDB.Root()
require.NoError(t, err, "%T.Root()", db)

rootFromInsert, err := newDB.Root()
require.NoError(t, err, "%T.Root() after insertion", db)

// Assert the hash is exactly as expected. Test failure indicates a
// non-hash compatible change has been made since the string was set.
// If that's expected, update the string at the top of the file to
Expand Down Expand Up @@ -415,16 +439,23 @@ func TestDeleteAll(t *testing.T) {
require.Empty(t, got, "Get(%d)", i)
}

emptyRootStr := expectedRoots[emptyKey]
expectedHash, err := hex.DecodeString(emptyRootStr)
require.NoError(t, err, "Decode expected empty root hash")

// TODO: Check proposal root
// Requires #918
// hash, err := proposal.Root()
// require.NoError(t, err, "%T.Root() after commit", proposal)
// require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db)

// Commit the proposal.
err = proposal.Commit()
require.NoError(t, err, "Commit")

// Check that the database is empty.
hash, err := db.Root()
require.NoError(t, err, "%T.Root()", db)
expectedHashHex := expectedRoots[emptyKey]
expectedHash, err := hex.DecodeString(expectedHashHex)
require.NoError(t, err)
require.Equalf(t, expectedHash, hash, "%T.Root() of empty trie", db)
}

Expand All @@ -445,27 +476,25 @@ func TestDropProposal(t *testing.T) {
err = proposal.Drop()
require.NoError(t, err, "Drop")

// Attempt to commit the dropped proposal.
// Check all operations on the dropped proposal.
err = proposal.Commit()
require.ErrorIs(t, err, errDroppedProposal, "Commit(dropped proposal)")

// Attempt to get a value from the dropped proposal.
_, err = proposal.Get([]byte("non-existent"))
require.ErrorIs(t, err, errDroppedProposal, "Get(dropped proposal)")
_, err = proposal.Root()
require.ErrorIs(t, err, errDroppedProposal, "Root(dropped proposal)")

// Attempt to "emulate" the proposal to ensure it isn't internally available still.
proposal = &Proposal{
handle: db.handle,
id: 1,
}

// Check all operations on the fake proposal.
_, err = proposal.Get([]byte("non-existent"))
require.Contains(t, err.Error(), "proposal not found", "Get(fake proposal)")

// Attempt to create a new proposal from the fake proposal.
_, err = proposal.Propose([][]byte{[]byte("key")}, [][]byte{[]byte("value")})
require.Contains(t, err.Error(), "proposal not found", "Propose(fake proposal)")

// Attempt to commit the fake proposal.
err = proposal.Commit()
require.Contains(t, err.Error(), "proposal not found", "Commit(fake proposal)")
}
Expand Down
Loading
Loading