Skip to content
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

Implement ldb database functionality for optional addr index. #11

Merged
merged 1 commit into from
Jan 26, 2015
Merged
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
41 changes: 41 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ import (

"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwire"
"golang.org/x/crypto/ripemd160"
)

// Errors that the various database functions may return.
var (
ErrAddrIndexDoesNotExist = errors.New("address index hasn't been built up yet")
ErrUnsupportedAddressType = errors.New("address type is not supported " +
"by the address-index")
ErrPrevShaMissing = errors.New("previous sha missing from database")
ErrTxShaMissing = errors.New("requested transaction does not exist")
ErrBlockShaMissing = errors.New("requested block does not exist")
ErrDuplicateSha = errors.New("duplicate insert attempted")
ErrDbDoesNotExist = errors.New("non-existent database")
ErrDbUnknownType = errors.New("non-existent database type")
ErrNotImplemented = errors.New("method has not yet been implemented")
)

// AllShas is a special value that can be used as the final sha when requesting
Expand Down Expand Up @@ -106,6 +111,34 @@ type Db interface {
// the database yet.
NewestSha() (sha *btcwire.ShaHash, height int64, err error)

// FetchAddrIndexTip returns the hash and block height of the most recent
// block which has had its address index populated. It will return
// ErrAddrIndexDoesNotExist along with a zero hash, and -1 if the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zero hash, or nil pointer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah technically it's actually a pointer to a btcwire.Shahash which is itself the zero hash. Full response below.

// addrindex hasn't yet been built up.
FetchAddrIndexTip() (sha *btcwire.ShaHash, height int64, err error)

// UpdateAddrIndexForBlock updates the stored addrindex with passed
// index information for a particular block height. Additionally, it
// will update the stored meta-data related to the curent tip of the
// addr index. These two operations are performed in an atomic
// transaction which is commited before the function returns.
// Addresses are indexed by the raw bytes of their base58 decoded
// hash160.
UpdateAddrIndexForBlock(blkSha *btcwire.ShaHash, height int64,
addrIndex BlockAddrIndex) error

// FetchTxsForAddr looks up and returns all transactions which either
// spend a previously created output of the passed address, or create
// a new output locked to the passed address. The, `limit` parameter
// should be the max number of transactions to be returned.
// Additionally, if the caller wishes to skip forward in the results
// some amount, the 'seek' represents how many results to skip.
// NOTE: Values for both `seek` and `limit` MUST be positive.
FetchTxsForAddr(addr btcutil.Address, skip int, limit int) ([]*TxListReply, error)

// DeleteAddrIndex deletes the entire addrindex stored within the DB.
DeleteAddrIndex() error

// RollbackClose discards the recent database changes to the previously
// saved data at last Sync and closes the database.
RollbackClose() (err error)
Expand Down Expand Up @@ -134,6 +167,14 @@ type TxListReply struct {
Err error
}

// AddrIndexKeySize is the number of bytes used by keys into the BlockAddrIndex.
const AddrIndexKeySize = ripemd160.Size

// BlockAddrIndex represents the indexing structure for addresses.
// It maps a hash160 to a list of transaction locations within a block that
// either pays to or spends from the passed UTXO for the hash160.
type BlockAddrIndex map[[AddrIndexKeySize]byte][]*btcwire.TxLoc

// driverList holds all of the registered database backends.
var driverList []DriverDB

Expand Down
2 changes: 1 addition & 1 deletion db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var (
ignoreDbTypes = map[string]bool{"createopenfail": true}
)

// testNewestShaEmpty ensures the NewestSha returns the values expected by
// testNewestShaEmpty ensures that NewestSha returns the values expected by
// the interface contract.
func testNewestShaEmpty(t *testing.T, db btcdb.Db) {
sha, height, err := db.NewestSha()
Expand Down
38 changes: 38 additions & 0 deletions ldb/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,41 @@ func (db *LevelDb) NewestSha() (rsha *btcwire.ShaHash, rblkid int64, err error)

return &sha, db.lastBlkIdx, nil
}

// fetchAddrIndexTip returns the last block height and block sha to be indexed.
// Meta-data about the address tip is currently cached in memory, and will be
// updated accordingly by functions that modify the state. This function is
// used on start up to load the info into memory. Callers will use the public
// version of this function below, which returns our cached copy.
func (db *LevelDb) fetchAddrIndexTip() (*btcwire.ShaHash, int64, error) {
db.dbLock.Lock()
defer db.dbLock.Unlock()

data, err := db.lDb.Get(addrIndexMetaDataKey, db.ro)
if err != nil {
return &btcwire.ShaHash{}, -1, btcdb.ErrAddrIndexDoesNotExist
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This answers my question above.. but why not return nil here instead? Otherwise we end up creating additional garbage for no good reason, for a value that shouldn't be touched to begin with.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point, returning nil instead does seem to be better behavior. It currently returns a pointer to the btcwire zero hash simply to emulate the behavior of NewestSha, for sake of consistency.

If changed to return nil, then following the same logic, should we also change NewestSha and update all callers the react to the new behavior? I think that'd require changes in both btcd and btcchain, and maybe some other packages I can't name off-head.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm true, I didn't realize NewestSha behaved the same way. I think it should be changed, but with the existing db interface being rewritten anyways, I wouldn't worry too much about it.

}

var blkSha btcwire.ShaHash
blkSha.SetBytes(data[0:32])

blkHeight := binary.LittleEndian.Uint64(data[32:])

return &blkSha, int64(blkHeight), nil
}

// FetchAddrIndexTip returns the hash and block height of the most recent
// block whose transactions have been indexed by address. It will return
// ErrAddrIndexDoesNotExist along with a zero hash, and -1 if the
// addrindex hasn't yet been built up.
func (db *LevelDb) FetchAddrIndexTip() (*btcwire.ShaHash, int64, error) {
db.dbLock.Lock()
defer db.dbLock.Unlock()

if db.lastAddrIndexBlkIdx == -1 {
return &btcwire.ShaHash{}, -1, btcdb.ErrAddrIndexDoesNotExist
}
sha := db.lastAddrIndexBlkSha

return &sha, db.lastAddrIndexBlkIdx, nil
}
3 changes: 2 additions & 1 deletion ldb/boundary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
"github.com/btcsuite/btcwire"
)

// we need to test for empty databas and make certain it returns proper value
// we need to test for an empty database and make certain it returns the proper
// values

func TestEmptyDB(t *testing.T) {

Expand Down
2 changes: 1 addition & 1 deletion ldb/dup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ out:
lastSha = blkSha
}

// genrate a new block based on the last sha
// generate a new block based on the last sha
// these block are not verified, so there are a bunch of garbage fields
// in the 'generated' block.

Expand Down
2 changes: 1 addition & 1 deletion ldb/insertremove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ endtest:
for height := int64(0); height < int64(len(blocks)); height++ {

block := blocks[height]
// look up inputs to this x
// look up inputs to this tx
mblock := block.MsgBlock()
var txneededList []*btcwire.ShaHash
var txlookupList []*btcwire.ShaHash
Expand Down
65 changes: 52 additions & 13 deletions ldb/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,60 @@
package ldb

import (
"fmt"
"bytes"

"github.com/btcsuite/btcdb"
"github.com/btcsuite/btcwire"
"testing"

"github.com/btcsuite/btcutil"
"golang.org/x/crypto/ripemd160"
)

// FetchSha returns the datablock and pver for the given ShaHash.
// This is a testing only interface.
func FetchSha(db btcdb.Db, sha *btcwire.ShaHash) (buf []byte, pver uint32,
blkid int64, err error) {
sqldb, ok := db.(*LevelDb)
if !ok {
err = fmt.Errorf("invalid data type")
return
func TestAddrIndexKeySerialization(t *testing.T) {
var hash160Bytes [ripemd160.Size]byte

fakeHash160 := btcutil.Hash160([]byte("testing"))
copy(fakeHash160, hash160Bytes[:])

fakeIndex := txAddrIndex{
hash160: hash160Bytes,
blkHeight: 1,
txoffset: 5,
txlen: 360,
}

serializedKey := addrIndexToKey(&fakeIndex)
unpackedIndex := unpackTxIndex(serializedKey[22:])

if unpackedIndex.blkHeight != fakeIndex.blkHeight {
t.Errorf("Incorrect block height. Unpack addr index key"+
"serialization failed. Expected %d, received %d",
1, unpackedIndex.blkHeight)
}

if unpackedIndex.txoffset != fakeIndex.txoffset {
t.Errorf("Incorrect tx offset. Unpack addr index key"+
"serialization failed. Expected %d, received %d",
5, unpackedIndex.txoffset)
}

if unpackedIndex.txlen != fakeIndex.txlen {
t.Errorf("Incorrect tx len. Unpack addr index key"+
"serialization failed. Expected %d, received %d",
360, unpackedIndex.txlen)
}
}

func TestBytesPrefix(t *testing.T) {
testKey := []byte("a")

prefixRange := bytesPrefix(testKey)
if !bytes.Equal(prefixRange.Start, []byte("a")) {
t.Errorf("Wrong prefix start, got %d, expected %d", prefixRange.Start,
[]byte("a"))
}

if !bytes.Equal(prefixRange.Limit, []byte("b")) {
t.Errorf("Wrong prefix end, got %d, expected %d", prefixRange.Limit,
[]byte("b"))
}
buf, blkid, err = sqldb.fetchSha(sha)
return
}
13 changes: 12 additions & 1 deletion ldb/leveldb.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ type LevelDb struct {
lastBlkSha btcwire.ShaHash
lastBlkIdx int64

lastAddrIndexBlkSha btcwire.ShaHash
lastAddrIndexBlkIdx int64

txUpdateMap map[btcwire.ShaHash]*txUpdateObj
txSpentUpdateMap map[btcwire.ShaHash]*spentTxUpdate
}
Expand Down Expand Up @@ -92,7 +95,6 @@ func OpenDB(args ...interface{}) (btcdb.Db, error) {
}

// Need to find last block and tx

var lastknownblock, nextunknownblock, testblock int64

increment := int64(100000)
Expand Down Expand Up @@ -139,6 +141,14 @@ blocknarrow:
}
}

// Load the last block whose transactions have been indexed by address.
if sha, idx, err := ldb.fetchAddrIndexTip(); err == nil {
ldb.lastAddrIndexBlkSha = *sha
ldb.lastAddrIndexBlkIdx = idx
} else {
ldb.lastAddrIndexBlkIdx = -1
}

ldb.lastBlkSha = *lastSha
ldb.lastBlkIdx = lastknownblock
ldb.nextBlock = lastknownblock + 1
Expand Down Expand Up @@ -250,6 +260,7 @@ func CreateDB(args ...interface{}) (btcdb.Db, error) {
if err == nil {
ldb := db.(*LevelDb)
ldb.lastBlkIdx = -1
ldb.lastAddrIndexBlkIdx = -1
ldb.nextBlock = 0
}
return db, err
Expand Down
Loading