Skip to content

Commit

Permalink
Use native DB range queries for applying validator diffs (#1752)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Jul 31, 2023
1 parent c7ec186 commit 1de061a
Show file tree
Hide file tree
Showing 13 changed files with 1,227 additions and 1,201 deletions.
25 changes: 17 additions & 8 deletions database/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,20 @@ import (

const (
Uint64Size = 8 // bytes
BoolSize = 1 // bytes
BoolFalse = 0x00
BoolTrue = 0x01

// kvPairOverhead is an estimated overhead for a kv pair in a database.
kvPairOverhead = 8 // bytes
)

var errWrongSize = errors.New("value has unexpected size")
var (
boolFalseKey = []byte{BoolFalse}
boolTrueKey = []byte{BoolTrue}

errWrongSize = errors.New("value has unexpected size")
)

func PutID(db KeyValueWriter, key []byte, val ids.ID) error {
return db.Put(key, val[:])
Expand Down Expand Up @@ -114,22 +123,22 @@ func ParseTimestamp(b []byte) (time.Time, error) {

func PutBool(db KeyValueWriter, key []byte, b bool) error {
if b {
return db.Put(key, []byte{1})
return db.Put(key, boolTrueKey)
}
return db.Put(key, []byte{0})
return db.Put(key, boolFalseKey)
}

func GetBool(db KeyValueReader, key []byte) (bool, error) {
b, err := db.Get(key)
switch {
case err != nil:
return false, err
case len(b) != 1:
return false, fmt.Errorf("length should be 1 but is %d", len(b))
case b[0] != 0 && b[0] != 1:
return false, fmt.Errorf("should be 0 or 1 but is %v", b[0])
case len(b) != BoolSize:
return false, fmt.Errorf("length should be %d but is %d", BoolSize, len(b))
case b[0] != BoolFalse && b[0] != BoolTrue:
return false, fmt.Errorf("should be %d or %d but is %d", BoolFalse, BoolTrue, b[0])
}
return b[0] == 1, nil
return b[0] == BoolTrue, nil
}

func Count(db Iteratee) (int, error) {
Expand Down
5 changes: 5 additions & 0 deletions scripts/build_fuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Mostly taken from https://github.com/golang/go/issues/46312#issuecomment-1153345129

# Directory above this script
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
# Load the constants
source "$AVALANCHE_PATH"/scripts/constants.sh

fuzzTime=${1:-1}
files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .)
failed=false
Expand Down
1 change: 1 addition & 0 deletions scripts/mocks.mockgen.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ github.com/ava-labs/avalanchego/vms/platformvm/blocks=Block=vms/platformvm/block
github.com/ava-labs/avalanchego/vms/platformvm/state=Chain=vms/platformvm/state/mock_chain.go
github.com/ava-labs/avalanchego/vms/platformvm/state=Diff=vms/platformvm/state/mock_diff.go
github.com/ava-labs/avalanchego/vms/platformvm/state=StakerIterator=vms/platformvm/state/mock_staker_iterator.go
github.com/ava-labs/avalanchego/vms/platformvm/state=State=vms/platformvm/state/mock_state.go
github.com/ava-labs/avalanchego/vms/platformvm/state=Versions=vms/platformvm/state/mock_versions.go
github.com/ava-labs/avalanchego/vms/platformvm/txs/builder=Builder=vms/platformvm/txs/builder/mock_builder.go
github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool=Mempool=vms/platformvm/txs/mempool/mock_mempool.go
Expand Down
2 changes: 0 additions & 2 deletions vms/platformvm/config/execution_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ var DefaultExecutionConfig = ExecutionConfig{
BlockCacheSize: 64 * units.MiB,
TxCacheSize: 128 * units.MiB,
TransformedSubnetTxCacheSize: 4 * units.MiB,
ValidatorDiffsCacheSize: 2048,
RewardUTXOsCacheSize: 2048,
ChainCacheSize: 2048,
ChainDBCacheSize: 2048,
Expand All @@ -25,7 +24,6 @@ type ExecutionConfig struct {
BlockCacheSize int `json:"block-cache-size"`
TxCacheSize int `json:"tx-cache-size"`
TransformedSubnetTxCacheSize int `json:"transformed-subnet-tx-cache-size"`
ValidatorDiffsCacheSize int `json:"validator-diffs-cache-size"`
RewardUTXOsCacheSize int `json:"reward-utxos-cache-size"`
ChainCacheSize int `json:"chain-cache-size"`
ChainDBCacheSize int `json:"chain-db-cache-size"`
Expand Down
2 changes: 0 additions & 2 deletions vms/platformvm/config/execution_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ func TestExecutionConfigUnmarshal(t *testing.T) {
"block-cache-size": 1,
"tx-cache-size": 2,
"transformed-subnet-tx-cache-size": 3,
"validator-diffs-cache-size": 4,
"reward-utxos-cache-size": 5,
"chain-cache-size": 6,
"chain-db-cache-size": 7,
Expand All @@ -54,7 +53,6 @@ func TestExecutionConfigUnmarshal(t *testing.T) {
BlockCacheSize: 1,
TxCacheSize: 2,
TransformedSubnetTxCacheSize: 3,
ValidatorDiffsCacheSize: 4,
RewardUTXOsCacheSize: 5,
ChainCacheSize: 6,
ChainDBCacheSize: 7,
Expand Down
97 changes: 97 additions & 0 deletions vms/platformvm/state/disk_staker_diff_iterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package state

import (
"encoding/binary"
"fmt"

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/ids"
)

const (
// startDiffKey = [subnetID] + [inverseHeight]
startDiffKeyLength = ids.IDLen + database.Uint64Size
// diffKey = [subnetID] + [inverseHeight] + [nodeID]
diffKeyLength = startDiffKeyLength + ids.NodeIDLen
// diffKeyNodeIDOffset = [subnetIDLen] + [inverseHeightLen]
diffKeyNodeIDOffset = ids.IDLen + database.Uint64Size

// weightValue = [isNegative] + [weight]
weightValueLength = database.BoolSize + database.Uint64Size
)

var (
errUnexpectedDiffKeyLength = fmt.Errorf("expected diff key length %d", diffKeyLength)
errUnexpectedWeightValueLength = fmt.Errorf("expected weight value length %d", weightValueLength)
)

// marshalStartDiffKey is used to determine the starting key when iterating.
//
// Invariant: the result is a prefix of [marshalDiffKey] when called with the
// same arguments.
func marshalStartDiffKey(subnetID ids.ID, height uint64) []byte {
key := make([]byte, startDiffKeyLength)
copy(key, subnetID[:])
packIterableHeight(key[ids.IDLen:], height)
return key
}

func marshalDiffKey(subnetID ids.ID, height uint64, nodeID ids.NodeID) []byte {
key := make([]byte, diffKeyLength)
copy(key, subnetID[:])
packIterableHeight(key[ids.IDLen:], height)
copy(key[diffKeyNodeIDOffset:], nodeID[:])
return key
}

func unmarshalDiffKey(key []byte) (ids.ID, uint64, ids.NodeID, error) {
if len(key) != diffKeyLength {
return ids.Empty, 0, ids.EmptyNodeID, errUnexpectedDiffKeyLength
}
var (
subnetID ids.ID
nodeID ids.NodeID
)
copy(subnetID[:], key)
height := unpackIterableHeight(key[ids.IDLen:])
copy(nodeID[:], key[diffKeyNodeIDOffset:])
return subnetID, height, nodeID, nil
}

func marshalWeightDiff(diff *ValidatorWeightDiff) []byte {
value := make([]byte, weightValueLength)
if diff.Decrease {
value[0] = database.BoolTrue
}
binary.BigEndian.PutUint64(value[database.BoolSize:], diff.Amount)
return value
}

func unmarshalWeightDiff(value []byte) (*ValidatorWeightDiff, error) {
if len(value) != weightValueLength {
return nil, errUnexpectedWeightValueLength
}
return &ValidatorWeightDiff{
Decrease: value[0] == database.BoolTrue,
Amount: binary.BigEndian.Uint64(value[database.BoolSize:]),
}, nil
}

// Note: [height] is encoded as a bit flipped big endian number so that
// iterating lexicographically results in iterating in decreasing heights.
//
// Invariant: [key] has sufficient length
func packIterableHeight(key []byte, height uint64) {
binary.BigEndian.PutUint64(key, ^height)
}

// Because we bit flip the height when constructing the key, we must remember to
// bip flip again here.
//
// Invariant: [key] has sufficient length
func unpackIterableHeight(key []byte) uint64 {
return ^binary.BigEndian.Uint64(key)
}
110 changes: 110 additions & 0 deletions vms/platformvm/state/disk_staker_diff_iterator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package state

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/thepudds/fzgen/fuzzer"

"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/ids"
)

func FuzzMarshalDiffKey(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
require := require.New(t)

var (
subnetID ids.ID
height uint64
nodeID ids.NodeID
)
fz := fuzzer.NewFuzzer(data)
fz.Fill(&subnetID, &height, &nodeID)

key := marshalDiffKey(subnetID, height, nodeID)
parsedSubnetID, parsedHeight, parsedNodeID, err := unmarshalDiffKey(key)
require.NoError(err)
require.Equal(subnetID, parsedSubnetID)
require.Equal(height, parsedHeight)
require.Equal(nodeID, parsedNodeID)
})
}

func FuzzUnmarshalDiffKey(f *testing.F) {
f.Fuzz(func(t *testing.T, key []byte) {
require := require.New(t)

subnetID, height, nodeID, err := unmarshalDiffKey(key)
if err != nil {
require.ErrorIs(err, errUnexpectedDiffKeyLength)
return
}

formattedKey := marshalDiffKey(subnetID, height, nodeID)
require.Equal(key, formattedKey)
})
}

func TestDiffIteration(t *testing.T) {
require := require.New(t)

db := memdb.New()

subnetID0 := ids.GenerateTestID()
subnetID1 := ids.GenerateTestID()

nodeID0 := ids.NodeID{0x00}
nodeID1 := ids.NodeID{0x01}

subnetID0Height0NodeID0 := marshalDiffKey(subnetID0, 0, nodeID0)
subnetID0Height1NodeID0 := marshalDiffKey(subnetID0, 1, nodeID0)
subnetID0Height1NodeID1 := marshalDiffKey(subnetID0, 1, nodeID1)

subnetID1Height0NodeID0 := marshalDiffKey(subnetID1, 0, nodeID0)
subnetID1Height1NodeID0 := marshalDiffKey(subnetID1, 1, nodeID0)
subnetID1Height1NodeID1 := marshalDiffKey(subnetID1, 1, nodeID1)

require.NoError(db.Put(subnetID0Height0NodeID0, nil))
require.NoError(db.Put(subnetID0Height1NodeID0, nil))
require.NoError(db.Put(subnetID0Height1NodeID1, nil))
require.NoError(db.Put(subnetID1Height0NodeID0, nil))
require.NoError(db.Put(subnetID1Height1NodeID0, nil))
require.NoError(db.Put(subnetID1Height1NodeID1, nil))

{
it := db.NewIteratorWithStartAndPrefix(marshalStartDiffKey(subnetID0, 0), subnetID0[:])
defer it.Release()

expectedKeys := [][]byte{
subnetID0Height0NodeID0,
}
for _, expectedKey := range expectedKeys {
require.True(it.Next())
require.Equal(expectedKey, it.Key())
}
require.False(it.Next())
require.NoError(it.Error())
}

{
it := db.NewIteratorWithStartAndPrefix(marshalStartDiffKey(subnetID0, 1), subnetID0[:])
defer it.Release()

expectedKeys := [][]byte{
subnetID0Height1NodeID0,
subnetID0Height1NodeID1,
subnetID0Height0NodeID0,
}
for _, expectedKey := range expectedKeys {
require.True(it.Next())
require.Equal(expectedKey, it.Key())
}
require.False(it.Next())
require.NoError(it.Error())
}
}
60 changes: 29 additions & 31 deletions vms/platformvm/state/mock_state.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1de061a

Please sign in to comment.