multi: change cfilter Extended bool to FilterType uint8

The cfilter BIP specifies that the filter type is a uint8. The
current code encodes it correctly on the wire, but everywhere else,
it's treated as a boolean (false for basic filter, true for
extended). This commit corrects that to account for possible
additional filter types in the future. All package changes are
done in one commit as they're all interdependent. The following
packages are updated:

* blockchain/indexers
* btcjson
* peer
* wire
* main (server.go and rpcserver.go)
This commit is contained in:
Alex 2017-09-13 14:42:24 +02:00 committed by Olaoluwa Osuntokun
parent e2f65acf02
commit 621c73dad1
11 changed files with 116 additions and 111 deletions

View file

@ -30,21 +30,21 @@ var (
// the index. The rest of the buckets live below this bucket.
cfIndexParentBucketKey = []byte("cfindexparentbucket")
// cfBasicIndexKey is the name of the db bucket used to house the
// block hash -> basic cf index (cf#0).
cfBasicIndexKey = []byte("cf0byhashidx")
// cfIndexKeys is an array of db bucket names used to house indexes of
// block hashes to cfilters.
cfIndexKeys = [][]byte{
[]byte("cf0byhashidx"),
[]byte("cf1byhashidx"),
}
// cfBasicHeaderKey is the name of the db bucket used to house the
// block hash -> basic cf header index (cf#0).
cfBasicHeaderKey = []byte("cf0headerbyhashidx")
// cfHeaderKeys is an array of db bucket names used to house indexes of
// block hashes to cf headers.
cfHeaderKeys = [][]byte{
[]byte("cf0headerbyhashidx"),
[]byte("cf1headerbyhashidx"),
}
// cfExtendedIndexKey is the name of the db bucket used to house the
// block hash -> extended cf index (cf#1).
cfExtendedIndexKey = []byte("cf1byhashidx")
// cfExtendedHeaderKey is the name of the db bucket used to house the
// block hash -> extended cf header index (cf#1).
cfExtendedHeaderKey = []byte("cf1headerbyhashidx")
maxFilterType = uint8(len(cfHeaderKeys) - 1)
)
// dbFetchFilter retrieves a block's basic or extended filter. A filter's
@ -131,27 +131,25 @@ func (idx *CfIndex) Create(dbTx database.Tx) error {
if err != nil {
return err
}
_, err = cfIndexParentBucket.CreateBucket(cfBasicIndexKey)
if err != nil {
return err
for _, bucketName := range cfIndexKeys {
_, err = cfIndexParentBucket.CreateBucket(bucketName)
if err != nil {
return err
}
}
_, err = cfIndexParentBucket.CreateBucket(cfBasicHeaderKey)
if err != nil {
return err
}
_, err = cfIndexParentBucket.CreateBucket(cfExtendedIndexKey)
if err != nil {
return err
}
_, err = cfIndexParentBucket.CreateBucket(cfExtendedHeaderKey)
if err != nil {
return err
for _, bucketName := range cfHeaderKeys {
_, err = cfIndexParentBucket.CreateBucket(bucketName)
if err != nil {
return err
}
}
firstHeader := make([]byte, chainhash.HashSize)
err = dbStoreFilterHeader(
dbTx,
cfBasicHeaderKey,
cfHeaderKeys[0],
&idx.chainParams.GenesisBlock.Header.PrevBlock,
firstHeader,
)
@ -161,7 +159,7 @@ func (idx *CfIndex) Create(dbTx database.Tx) error {
return dbStoreFilterHeader(
dbTx,
cfExtendedHeaderKey,
cfHeaderKeys[1],
&idx.chainParams.GenesisBlock.Header.PrevBlock,
firstHeader,
)
@ -170,15 +168,14 @@ func (idx *CfIndex) Create(dbTx database.Tx) error {
// storeFilter stores a given filter, and performs the steps needed to
// generate the filter's header.
func storeFilter(dbTx database.Tx, block *btcutil.Block, f *gcs.Filter,
extended bool) error {
filterType uint8) error {
if filterType > maxFilterType {
return errors.New("unsupported filter type")
}
// Figure out which buckets to use.
fkey := cfBasicIndexKey
hkey := cfBasicHeaderKey
if extended {
fkey = cfExtendedIndexKey
hkey = cfExtendedHeaderKey
}
fkey := cfIndexKeys[filterType]
hkey := cfHeaderKeys[filterType]
// Start by storing the filter.
h := block.Hash()
@ -218,7 +215,7 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
return err
}
if err := storeFilter(dbTx, block, f, false); err != nil {
if err := storeFilter(dbTx, block, f, 0); err != nil {
return err
}
@ -227,7 +224,7 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
return err
}
return storeFilter(dbTx, block, f, true)
return storeFilter(dbTx, block, f, 1)
}
// DisconnectBlock is invoked by the index manager when a block has been
@ -236,26 +233,34 @@ func (idx *CfIndex) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
func (idx *CfIndex) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
view *blockchain.UtxoViewpoint) error {
err := dbDeleteFilter(dbTx, cfBasicIndexKey, block.Hash())
if err != nil {
return err
for _, key := range cfIndexKeys {
err := dbDeleteFilter(dbTx, key, block.Hash())
if err != nil {
return err
}
}
return dbDeleteFilter(dbTx, cfExtendedIndexKey, block.Hash())
for _, key := range cfHeaderKeys {
err := dbDeleteFilterHeader(dbTx, key, block.Hash())
if err != nil {
return err
}
}
return nil
}
// FilterByBlockHash returns the serialized contents of a block's basic or
// extended committed filter.
func (idx *CfIndex) FilterByBlockHash(h *chainhash.Hash, extended bool) ([]byte, error) {
func (idx *CfIndex) FilterByBlockHash(h *chainhash.Hash, filterType uint8) ([]byte, error) {
var f []byte
err := idx.db.View(func(dbTx database.Tx) error {
var err error
key := cfBasicIndexKey
if extended {
key = cfExtendedIndexKey
if filterType > maxFilterType {
return errors.New("unsupported filter type")
}
f, err = dbFetchFilter(dbTx, key, h)
var err error
f, err = dbFetchFilter(dbTx, cfIndexKeys[filterType], h)
return err
})
return f, err
@ -263,16 +268,15 @@ func (idx *CfIndex) FilterByBlockHash(h *chainhash.Hash, extended bool) ([]byte,
// FilterHeaderByBlockHash returns the serialized contents of a block's basic
// or extended committed filter header.
func (idx *CfIndex) FilterHeaderByBlockHash(h *chainhash.Hash, extended bool) ([]byte, error) {
func (idx *CfIndex) FilterHeaderByBlockHash(h *chainhash.Hash, filterType uint8) ([]byte, error) {
var fh []byte
err := idx.db.View(func(dbTx database.Tx) error {
var err error
key := cfBasicHeaderKey
if extended {
key = cfExtendedHeaderKey
if filterType > 1 {
return errors.New("unsupported filter type")
}
fh, err = dbFetchFilterHeader(dbTx, key, h)
var err error
fh, err = dbFetchFilterHeader(dbTx, cfHeaderKeys[filterType], h)
return err
})
return fh, err

View file

@ -278,33 +278,34 @@ func NewGetBlockTemplateCmd(request *TemplateRequest) *GetBlockTemplateCmd {
Request: request,
}
}
// GetCFilterCmd defines the getcfilter JSON-RPC command.
type GetCFilterCmd struct {
Hash string
Extended bool
Hash string
FilterType uint8
}
// NewGetCFilterCmd returns a new instance which can be used to issue a
// getcfilter JSON-RPC command.
func NewGetCFilterCmd(hash string, extended bool) *GetCFilterCmd {
func NewGetCFilterCmd(hash string, filterType uint8) *GetCFilterCmd {
return &GetCFilterCmd{
Hash: hash,
Extended: extended,
Hash: hash,
FilterType: filterType,
}
}
// GetCFilterHeaderCmd defines the getcfilterheader JSON-RPC command.
type GetCFilterHeaderCmd struct {
Hash string
Extended bool
Hash string
FilterType uint8
}
// NewGetCFilterHeaderCmd returns a new instance which can be used to issue a
// getcfilterheader JSON-RPC command.
func NewGetCFilterHeaderCmd(hash string, extended bool) *GetCFilterHeaderCmd {
func NewGetCFilterHeaderCmd(hash string, filterType uint8) *GetCFilterHeaderCmd {
return &GetCFilterHeaderCmd{
Hash: hash,
Extended: extended,
Hash: hash,
FilterType: filterType,
}
}

View file

@ -320,27 +320,27 @@ func TestChainSvrCmds(t *testing.T) {
{
name: "getcfilter",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getcfilter", "123", false)
return btcjson.NewCmd("getcfilter", "123", 0)
},
staticCmd: func() interface{} {
return btcjson.NewGetCFilterCmd("123", false)
return btcjson.NewGetCFilterCmd("123", 0)
},
marshalled: `{"jsonrpc":"1.0","method":"getcfilter","params":["123",false],"id":1}`,
unmarshalled: &btcjson.GetCFilterCmd{
Hash: "123",
Hash: "123",
},
},
{
name: "getcfilterheader",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getcfilterheader", "123", false)
return btcjson.NewCmd("getcfilterheader", "123", 0)
},
staticCmd: func() interface{} {
return btcjson.NewGetCFilterHeaderCmd("123", false)
return btcjson.NewGetCFilterHeaderCmd("123", 0)
},
marshalled: `{"jsonrpc":"1.0","method":"getcfilterheader","params":["123",false],"id":1}`,
unmarshalled: &btcjson.GetCFilterHeaderCmd{
Hash: "123",
Hash: "123",
},
},
{

View file

@ -536,7 +536,7 @@ func TestPeerListeners(t *testing.T) {
},
{
"OnGetCFilter",
wire.NewMsgGetCFilter(&chainhash.Hash{}, false),
wire.NewMsgGetCFilter(&chainhash.Hash{}, 0),
},
{
"OnGetCFHeaders",
@ -544,7 +544,7 @@ func TestPeerListeners(t *testing.T) {
},
{
"OnCFilter",
wire.NewMsgCFilter(&chainhash.Hash{}, true,
wire.NewMsgCFilter(&chainhash.Hash{}, 1,
[]byte("payload")),
},
{

View file

@ -2163,7 +2163,7 @@ func handleGetCFilter(s *rpcServer, cmd interface{}, closeChan <-chan struct{})
return nil, rpcDecodeHexError(c.Hash)
}
filterBytes, err := s.cfg.CfIndex.FilterByBlockHash(hash, c.Extended)
filterBytes, err := s.cfg.CfIndex.FilterByBlockHash(hash, c.FilterType)
if err != nil {
rpcsLog.Debugf("Could not find committed filter for %v: %v",
hash, err)
@ -2192,7 +2192,7 @@ func handleGetCFilterHeader(s *rpcServer, cmd interface{}, closeChan <-chan stru
return nil, rpcDecodeHexError(c.Hash)
}
headerBytes, err := s.cfg.CfIndex.FilterHeaderByBlockHash(hash, c.Extended)
headerBytes, err := s.cfg.CfIndex.FilterHeaderByBlockHash(hash, c.FilterType)
if len(headerBytes) > 0 {
rpcsLog.Debugf("Found header of committed filter for %v", hash)
} else {

View file

@ -747,7 +747,7 @@ func (sp *serverPeer) OnGetCFilter(_ *peer.Peer, msg *wire.MsgGetCFilter) {
}
filterBytes, err := sp.server.cfIndex.FilterByBlockHash(&msg.BlockHash,
msg.Extended)
msg.FilterType)
if len(filterBytes) > 0 {
peerLog.Tracef("Obtained CF for %v", msg.BlockHash)
@ -756,7 +756,7 @@ func (sp *serverPeer) OnGetCFilter(_ *peer.Peer, msg *wire.MsgGetCFilter) {
err)
}
filterMsg := wire.NewMsgCFilter(&msg.BlockHash, msg.Extended,
filterMsg := wire.NewMsgCFilter(&msg.BlockHash, msg.FilterType,
filterBytes)
sp.QueueMessage(filterMsg, nil)
}
@ -789,7 +789,7 @@ func (sp *serverPeer) OnGetCFHeaders(_ *peer.Peer, msg *wire.MsgGetCFHeaders) {
// Fetch the raw committed filter header bytes from the
// database.
headerBytes, err := sp.server.cfIndex.FilterHeaderByBlockHash(
&msg.HashStop, msg.Extended)
&msg.HashStop, msg.FilterType)
if (err != nil) || (len(headerBytes) == 0) {
peerLog.Warnf("Could not obtain CF header for %v: %v",
msg.HashStop, err)
@ -808,7 +808,7 @@ func (sp *serverPeer) OnGetCFHeaders(_ *peer.Peer, msg *wire.MsgGetCFHeaders) {
headersMsg := wire.NewMsgCFHeaders()
headersMsg.AddCFHeader(&header)
headersMsg.StopHash = msg.HashStop
headersMsg.Extended = msg.Extended
headersMsg.FilterType = msg.FilterType
sp.QueueMessage(headersMsg, nil)
return
}
@ -849,7 +849,7 @@ func (sp *serverPeer) OnGetCFHeaders(_ *peer.Peer, msg *wire.MsgGetCFHeaders) {
// Fetch the raw committed filter header bytes from the
// database.
headerBytes, err := sp.server.cfIndex.FilterHeaderByBlockHash(
&hashList[i], msg.Extended)
&hashList[i], msg.FilterType)
if (err != nil) || (len(headerBytes) == 0) {
peerLog.Warnf("Could not obtain CF header for %v: %v",
hashList[i], err)
@ -868,7 +868,7 @@ func (sp *serverPeer) OnGetCFHeaders(_ *peer.Peer, msg *wire.MsgGetCFHeaders) {
headersMsg.AddCFHeader(&header)
}
headersMsg.Extended = msg.Extended
headersMsg.FilterType = msg.FilterType
headersMsg.StopHash = hashList[len(hashList)-1]
sp.QueueMessage(headersMsg, nil)
}

View file

@ -69,9 +69,9 @@ func TestMessage(t *testing.T) {
bh := NewBlockHeader(1, &chainhash.Hash{}, &chainhash.Hash{}, 0, 0)
msgMerkleBlock := NewMsgMerkleBlock(bh)
msgReject := NewMsgReject("block", RejectDuplicate, "duplicate block")
msgGetCFilter := NewMsgGetCFilter(&chainhash.Hash{}, false)
msgGetCFilter := NewMsgGetCFilter(&chainhash.Hash{}, 0)
msgGetCFHeaders := NewMsgGetCFHeaders()
msgCFilter := NewMsgCFilter(&chainhash.Hash{}, true, []byte("payload"))
msgCFilter := NewMsgCFilter(&chainhash.Hash{}, 1, []byte("payload"))
msgCFHeaders := NewMsgCFHeaders()
tests := []struct {

View file

@ -28,7 +28,7 @@ const (
// MsgGetCFHeaders for details on requesting the headers.
type MsgCFHeaders struct {
StopHash chainhash.Hash
Extended bool
FilterType uint8
HeaderHashes []*chainhash.Hash
}
@ -53,8 +53,8 @@ func (msg *MsgCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding)
return err
}
// Read extended flag
err = readElement(r, &msg.Extended)
// Read filter type
err = readElement(r, &msg.FilterType)
if err != nil {
return err
}
@ -97,8 +97,8 @@ func (msg *MsgCFHeaders) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding)
return err
}
// Write extended flag
err = writeElement(w, msg.Extended)
// Write filter type
err = writeElement(w, msg.FilterType)
if err != nil {
return err
}

View file

@ -17,9 +17,9 @@ const (
)
type MsgCFilter struct {
BlockHash chainhash.Hash
Extended bool
Data []byte
BlockHash chainhash.Hash
FilterType uint8
Data []byte
}
// BtcDecode decodes r using the bitcoin protocol encoding into the receiver.
@ -31,8 +31,8 @@ func (msg *MsgCFilter) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) er
if err != nil {
return err
}
// Read extended flag
err = readElement(r, &msg.Extended)
// Read filter type
err = readElement(r, &msg.FilterType)
if err != nil {
return err
}
@ -57,7 +57,7 @@ func (msg *MsgCFilter) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding) er
return err
}
err = writeElement(w, msg.Extended)
err = writeElement(w, msg.FilterType)
if err != nil {
return err
}
@ -96,11 +96,11 @@ func (msg *MsgCFilter) MaxPayloadLength(pver uint32) uint32 {
// NewMsgCFilter returns a new bitcoin cfilter message that conforms to the
// Message interface. See MsgCFilter for details.
func NewMsgCFilter(blockHash *chainhash.Hash, extended bool,
func NewMsgCFilter(blockHash *chainhash.Hash, filterType uint8,
data []byte) *MsgCFilter {
return &MsgCFilter{
BlockHash: *blockHash,
Extended: extended,
Data: data,
BlockHash: *blockHash,
FilterType: filterType,
Data: data,
}
}

View file

@ -12,13 +12,13 @@ import (
)
// MsgGetCFHeaders is a message similar to MsgGetHeaders, but for committed
// filter headers. It allows to set the Extended field to get headers in the
// chain of basic (false) or extended (true) headers.
// filter headers. It allows to set the FilterType field to get headers in the
// chain of basic (0x00) or extended (0x01) headers.
type MsgGetCFHeaders struct {
ProtocolVersion uint32
BlockLocatorHashes []*chainhash.Hash
HashStop chainhash.Hash
Extended bool
FilterType uint8
}
// AddBlockLocatorHash adds a new block locator hash to the message.
@ -70,7 +70,7 @@ func (msg *MsgGetCFHeaders) BtcDecode(r io.Reader, pver uint32, _ MessageEncodin
return err
}
return readElement(r, &msg.Extended)
return readElement(r, &msg.FilterType)
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
@ -106,7 +106,7 @@ func (msg *MsgGetCFHeaders) BtcEncode(w io.Writer, pver uint32, _ MessageEncodin
return err
}
return writeElement(w, msg.Extended)
return writeElement(w, msg.FilterType)
}
// Command returns the protocol command string for the message. This is part
@ -119,7 +119,7 @@ func (msg *MsgGetCFHeaders) Command() string {
// receiver. This is part of the Message interface implementation.
func (msg *MsgGetCFHeaders) MaxPayloadLength(pver uint32) uint32 {
// Version 4 bytes + num block locator hashes (varInt) + max allowed
// block locators + hash stop + Extended flag 1 byte.
// block locators + hash stop + filter type 1 byte.
return 4 + MaxVarIntPayload + (MaxBlockLocatorsPerMsg *
chainhash.HashSize) + chainhash.HashSize + 1
}

View file

@ -11,8 +11,8 @@ import (
)
type MsgGetCFilter struct {
BlockHash chainhash.Hash
Extended bool
BlockHash chainhash.Hash
FilterType uint8
}
func (msg *MsgGetCFilter) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding) error {
@ -20,7 +20,7 @@ func (msg *MsgGetCFilter) BtcDecode(r io.Reader, pver uint32, _ MessageEncoding)
if err != nil {
return err
}
return readElement(r, &msg.Extended)
return readElement(r, &msg.FilterType)
}
// BtcEncode encodes the receiver to w using the bitcoin protocol encoding.
@ -30,7 +30,7 @@ func (msg *MsgGetCFilter) BtcEncode(w io.Writer, pver uint32, _ MessageEncoding)
if err != nil {
return err
}
return writeElement(w, msg.Extended)
return writeElement(w, msg.FilterType)
}
// Command returns the protocol command string for the message. This is part
@ -42,16 +42,16 @@ func (msg *MsgGetCFilter) Command() string {
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgGetCFilter) MaxPayloadLength(pver uint32) uint32 {
// Block hash + Extended flag.
// Block hash + filter type.
return chainhash.HashSize + 1
}
// NewMsgGetCFilter returns a new bitcoin getcfilter message that conforms to
// the Message interface using the passed parameters and defaults for the
// remaining fields.
func NewMsgGetCFilter(blockHash *chainhash.Hash, extended bool) *MsgGetCFilter {
func NewMsgGetCFilter(blockHash *chainhash.Hash, filterType uint8) *MsgGetCFilter {
return &MsgGetCFilter{
BlockHash: *blockHash,
Extended: extended,
BlockHash: *blockHash,
FilterType: filterType,
}
}