blockchain: Add utxoDatabaseInfo.

The adds utxoDatabaseInfo which tracks the version, compression version,
utxo set version, and creation time of the UTXO database.  The UTXO
database will be created as a separate database in a subsequent commit.

This is part of moving the UTXO set and state to a separate database.
This commit is contained in:
Ryan Staudt 2021-04-17 15:34:22 -05:00 committed by Dave Collins
parent c7315b6bf6
commit 105f0bad80
3 changed files with 211 additions and 4 deletions

View File

@ -24,12 +24,11 @@ import (
)
const (
// currentDatabaseVersion indicates what the current database
// version is.
// currentDatabaseVersion indicates the current database version.
currentDatabaseVersion = 9
// currentBlockIndexVersion indicates what the current block index
// database version.
// currentBlockIndexVersion indicates the current block index database
// version.
currentBlockIndexVersion = 3
// blockHdrSize is the size of a block header. This is simply the

View File

@ -8,6 +8,7 @@ package blockchain
import (
"fmt"
"sync"
"time"
"github.com/decred/dcrd/blockchain/standalone/v2"
"github.com/decred/dcrd/chaincfg/chainhash"
@ -16,6 +17,29 @@ import (
)
var (
// utxoDbInfoBucketName is the name of the database bucket used to house
// global versioning and date information for the UTXO database.
utxoDbInfoBucketName = []byte("dbinfo")
// utxoDbInfoVersionKeyName is the name of the database key used to house the
// database version. It is itself under the utxoDbInfoBucketName bucket.
utxoDbInfoVersionKeyName = []byte("version")
// utxoDbInfoCompressionVerKeyName is the name of the database key used to
// house the database compression version. It is itself under the
// utxoDbInfoBucketName bucket.
utxoDbInfoCompressionVerKeyName = []byte("compver")
// utxoDbInfoUtxoVerKeyName is the name of the database key used to house the
// database UTXO set version. It is itself under the utxoDbInfoBucketName
// bucket.
utxoDbInfoUtxoVerKeyName = []byte("utxover")
// utxoDbInfoCreatedKeyName is the name of the database key used to house
// the date the database was created. It is itself under the
// utxoDbInfoBucketName bucket.
utxoDbInfoCreatedKeyName = []byte("created")
// utxoSetBucketName is the name of the db bucket used to house the unspent
// transaction output set.
utxoSetBucketName = []byte("utxosetv3")
@ -25,6 +49,120 @@ var (
utxoSetStateKeyName = []byte("utxosetstate")
)
// -----------------------------------------------------------------------------
// The UTXO database information contains information about the version and date
// of the UTXO database.
//
// It consists of a separate key for each individual piece of information:
//
// Key Value Size Description
// version uint32 4 bytes The version of the database
// compver uint32 4 bytes The script compression version of the database
// utxover uint32 4 bytes The UTXO set version of the database
// created uint64 8 bytes The date of the creation of the database
// -----------------------------------------------------------------------------
// utxoDatabaseInfo is the structure that holds the versioning and date
// information for the UTXO database.
type utxoDatabaseInfo struct {
version uint32
compVer uint32
utxoVer uint32
created time.Time
}
// dbPutUtxoDatabaseInfo uses an existing database transaction to store the
// database information.
func dbPutUtxoDatabaseInfo(dbTx database.Tx, dbi *utxoDatabaseInfo) error {
// uint32Bytes is a helper function to convert a uint32 to a byte slice
// using the byte order specified by the database namespace.
uint32Bytes := func(ui32 uint32) []byte {
var b [4]byte
byteOrder.PutUint32(b[:], ui32)
return b[:]
}
// uint64Bytes is a helper function to convert a uint64 to a byte slice
// using the byte order specified by the database namespace.
uint64Bytes := func(ui64 uint64) []byte {
var b [8]byte
byteOrder.PutUint64(b[:], ui64)
return b[:]
}
// Store the database version.
meta := dbTx.Metadata()
bucket := meta.Bucket(utxoDbInfoBucketName)
err := bucket.Put(utxoDbInfoVersionKeyName, uint32Bytes(dbi.version))
if err != nil {
return err
}
// Store the compression version.
err = bucket.Put(utxoDbInfoCompressionVerKeyName, uint32Bytes(dbi.compVer))
if err != nil {
return err
}
// Store the UTXO set version.
err = bucket.Put(utxoDbInfoUtxoVerKeyName, uint32Bytes(dbi.utxoVer))
if err != nil {
return err
}
// Store the database creation date.
return bucket.Put(utxoDbInfoCreatedKeyName,
uint64Bytes(uint64(dbi.created.Unix())))
}
// dbFetchUtxoDatabaseInfo uses an existing database transaction to fetch the
// database versioning and creation information.
func dbFetchUtxoDatabaseInfo(dbTx database.Tx) *utxoDatabaseInfo {
meta := dbTx.Metadata()
bucket := meta.Bucket(utxoDbInfoBucketName)
// Uninitialized state.
if bucket == nil {
return nil
}
// Load the database version.
var version uint32
versionBytes := bucket.Get(utxoDbInfoVersionKeyName)
if versionBytes != nil {
version = byteOrder.Uint32(versionBytes)
}
// Load the database compression version.
var compVer uint32
compVerBytes := bucket.Get(utxoDbInfoCompressionVerKeyName)
if compVerBytes != nil {
compVer = byteOrder.Uint32(compVerBytes)
}
// Load the database UTXO set version.
var utxoVer uint32
utxoVerBytes := bucket.Get(utxoDbInfoUtxoVerKeyName)
if utxoVerBytes != nil {
utxoVer = byteOrder.Uint32(utxoVerBytes)
}
// Load the database creation date.
var created time.Time
createdBytes := bucket.Get(utxoDbInfoCreatedKeyName)
if createdBytes != nil {
ts := byteOrder.Uint64(createdBytes)
created = time.Unix(int64(ts), 0)
}
return &utxoDatabaseInfo{
version: version,
compVer: compVer,
utxoVer: utxoVer,
created: created,
}
}
// -----------------------------------------------------------------------------
// The unspent transaction output (utxo) set consists of an entry for each
// unspent output using a format that is optimized to reduce space using domain

View File

@ -12,12 +12,82 @@ import (
"path/filepath"
"reflect"
"testing"
"time"
"github.com/decred/dcrd/blockchain/stake/v4"
"github.com/decred/dcrd/database/v2"
"github.com/decred/dcrd/wire"
)
// TestDbFetchUtxoDatabaseInfo ensures that putting and fetching the utxo
// database info works as expected.
func TestDbFetchUtxoDatabaseInfo(t *testing.T) {
t.Parallel()
// Create a test database.
dbPath := filepath.Join(os.TempDir(), "test-dbfetchutxodatabaseinfo")
_ = os.RemoveAll(dbPath)
db, err := database.Create("ffldb", dbPath, wire.MainNet)
if err != nil {
t.Fatalf("error creating test database: %v", err)
}
defer os.RemoveAll(dbPath)
defer db.Close()
tests := []struct {
name string
dbInfo *utxoDatabaseInfo
}{{
name: "without UTXO database info (fresh database)",
dbInfo: nil,
}, {
name: "with UTXO database info",
dbInfo: &utxoDatabaseInfo{
version: 1,
compVer: 2,
utxoVer: 3,
created: time.Unix(1584246683, 0), // 2020-03-15 04:31:23 UTC
},
}}
for _, test := range tests {
if test.dbInfo != nil {
// Create the UTXO database info bucket.
err = db.Update(func(dbTx database.Tx) error {
_, err := dbTx.Metadata().CreateBucketIfNotExists(utxoDbInfoBucketName)
return err
})
if err != nil {
t.Fatalf("%q: error creating UTXO bucket: %v", test.name, err)
}
// Update the UTXO database info in the database.
err = db.Update(func(dbTx database.Tx) error {
return dbPutUtxoDatabaseInfo(dbTx, test.dbInfo)
})
if err != nil {
t.Fatalf("%q: error putting UTXO database info: %v", test.name, err)
}
}
// Fetch the UTXO database info from the database.
err = db.View(func(dbTx database.Tx) error {
gotDbInfo := dbFetchUtxoDatabaseInfo(dbTx)
// Ensure that the fetched UTXO database info matches the expected UTXO
// database info.
if !reflect.DeepEqual(gotDbInfo, test.dbInfo) {
t.Fatalf("%q: mismatched db info:\nwant: %+v\n got: %+v\n", test.name,
test.dbInfo, gotDbInfo)
}
return nil
})
if err != nil {
t.Fatalf("%q: error fetching UTXO database info: %v", test.name, err)
}
}
}
// TestUtxoSerialization ensures serializing and deserializing unspent
// transaction output entries works as expected.
func TestUtxoSerialization(t *testing.T) {