Skip to content

Commit

Permalink
Fix bugs and add SendTransaction, which isn't that great.
Browse files Browse the repository at this point in the history
  • Loading branch information
aakselrod committed May 12, 2017
1 parent 92ae144 commit 5427079
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 263 deletions.
4 changes: 2 additions & 2 deletions glide.lock

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

73 changes: 61 additions & 12 deletions spvsvc/spvchain/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package spvchain

import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -207,7 +209,8 @@ func (s *ChainService) queryPeers(
// to the channel if we quit before
// reading the channel.
sentChan := make(chan struct{}, 1)
sp.QueueMessage(queryMsg, sentChan)
sp.QueueMessageWithEncoding(queryMsg,
sentChan, wire.WitnessEncoding)
select {
case <-sentChan:
case <-quit:
Expand Down Expand Up @@ -279,7 +282,7 @@ checkResponses:
// GetCFilter gets a cfilter from the database. Failing that, it requests the
// cfilter from the network and writes it to the database.
func (s *ChainService) GetCFilter(blockHash chainhash.Hash,
extended bool, options ...QueryOption) *gcs.Filter {
extended bool, options ...QueryOption) (*gcs.Filter, error) {
getFilter := s.GetBasicFilter
getHeader := s.GetBasicHeader
putFilter := s.putBasicFilter
Expand All @@ -290,22 +293,34 @@ func (s *ChainService) GetCFilter(blockHash chainhash.Hash,
}
filter, err := getFilter(blockHash)
if err == nil && filter != nil {
return filter
return filter, nil
}
// We didn't get the filter from the DB, so we'll set it to nil and try
// to get it from the network.
filter = nil
block, _, err := s.GetBlockByHash(blockHash)
if err != nil || block.BlockHash() != blockHash {
return nil
if err != nil {
return nil, err
}
if block.BlockHash() != blockHash {
return nil, fmt.Errorf("Couldn't get header for block %s "+
"from database", blockHash)
}
curHeader, err := getHeader(blockHash)
if err != nil {
return nil
return nil, fmt.Errorf("Couldn't get cfheader for block %s "+
"from database", blockHash)
}
prevHeader, err := getHeader(block.PrevBlock)
if err != nil {
return nil
return nil, fmt.Errorf("Couldn't get cfheader for block %s "+
"from database", blockHash)
}
// If we're expecting a zero filter, just return a nil filter and don't
// bother trying to get it from the network. The caller will know
// there's no error because we're also returning a nil error.
if builder.MakeHeaderForFilter(nil, *prevHeader) == *curHeader {
return nil, nil
}
s.queryPeers(
// Send a wire.GetCFilterMsg
Expand Down Expand Up @@ -364,19 +379,21 @@ func (s *ChainService) GetCFilter(blockHash chainhash.Hash,
log.Tracef("Wrote filter for block %s, extended: %t",
blockHash, extended)
}
return filter
return filter, nil
}

// GetBlockFromNetwork gets a block by requesting it from the network, one peer
// at a time, until one answers.
func (s *ChainService) GetBlockFromNetwork(
blockHash chainhash.Hash, options ...QueryOption) *btcutil.Block {
blockHash chainhash.Hash, options ...QueryOption) (*btcutil.Block,
error) {
blockHeader, height, err := s.GetBlockByHash(blockHash)
if err != nil || blockHeader.BlockHash() != blockHash {
return nil
return nil, fmt.Errorf("Couldn't get header for block %s "+
"from database", blockHash)
}
getData := wire.NewMsgGetData()
getData.AddInvVect(wire.NewInvVect(wire.InvTypeBlock,
getData.AddInvVect(wire.NewInvVect(wire.InvTypeWitnessBlock,
&blockHash))
// The block is only updated from the checkResponse function argument,
// which is always called single-threadedly. We don't check the block
Expand Down Expand Up @@ -441,5 +458,37 @@ func (s *ChainService) GetBlockFromNetwork(
},
options...,
)
return foundBlock
if foundBlock == nil {
return nil, fmt.Errorf("Couldn't retrieve block %s from "+
"network", blockHash)
}
return foundBlock, nil
}

// SendTransaction sends a transaction to each peer. It returns an error if any
// peer rejects the transaction for any reason than that it's already known.
// TODO: Better privacy by sending to only one random peer and watching
// propagation, requires better peer selection support in query API.
func (s *ChainService) SendTransaction(tx *wire.MsgTx,
options ...QueryOption) error {
var err error
s.queryPeers(
tx,
func(sp *serverPeer, resp wire.Message, quit chan<- struct{}) {
switch response := resp.(type) {
case *wire.MsgReject:
if response.Hash == tx.TxHash() &&
!strings.Contains(response.Reason,
"already have transaction") {
err = log.Errorf("Transaction %s "+
"rejected by %s: %s",
tx.TxHash(), sp.Addr(),
response.Reason)
close(quit)
}
}
},
options...,
)
return err
}
58 changes: 37 additions & 21 deletions spvsvc/spvchain/rescan.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,10 @@ rescanLoop:
var err error
key := builder.DeriveKey(&curStamp.Hash)
matched := false
bFilter = s.GetCFilter(curStamp.Hash, false)
bFilter, err = s.GetCFilter(curStamp.Hash, false)
if err != nil {
return err
}
if bFilter != nil && bFilter.N() != 0 {
// We see if any relevant transactions match.
matched, err = bFilter.MatchAny(key, watchList)
Expand All @@ -329,7 +332,10 @@ rescanLoop:
}
}
if len(ro.watchTXIDs) > 0 {
eFilter = s.GetCFilter(curStamp.Hash, true)
eFilter, err = s.GetCFilter(curStamp.Hash, true)
if err != nil {
return err
}
}
if eFilter != nil && eFilter.N() != 0 {
// We see if any relevant transactions match.
Expand All @@ -345,11 +351,14 @@ rescanLoop:
// We've matched. Now we actually get the block
// and cycle through the transactions to see
// which ones are relevant.
block = s.GetBlockFromNetwork(
block, err = s.GetBlockFromNetwork(
curStamp.Hash, ro.queryOptions...)
if err != nil {
return err
}
if block == nil {
return fmt.Errorf("Couldn't get block "+
"%d (%s)", curStamp.Height,
return fmt.Errorf("Couldn't get block %d "+
"(%s) from network", curStamp.Height,
curStamp.Hash)
}
relevantTxs, err = notifyBlock(block,
Expand Down Expand Up @@ -409,9 +418,7 @@ func notifyBlock(block *btcutil.Block, outPoints *[]wire.OutPoint,
}
}
for outIdx, out := range tx.MsgTx().TxOut {
pushedData, err :=
txscript.PushedData(
out.PkScript)
pushedData, err := txscript.PushedData(out.PkScript)
if err != nil {
continue
}
Expand Down Expand Up @@ -503,42 +510,51 @@ func (s *ChainService) GetUtxo(options ...RescanOption) (*wire.TxOut, error) {
}
}
log.Tracef("Starting scan for output spend from known block %d (%s) "+
"back to block %d (%s)", curStamp.Height, curStamp.Hash)
"back to block %d (%s)", curStamp.Height, curStamp.Hash,
ro.startBlock.Height, ro.startBlock.Hash)

for {
// Check the basic filter for the spend and the extended filter
// for the transaction in which the outpout is funded.
filter := s.GetCFilter(curStamp.Hash, false,
filter, err := s.GetCFilter(curStamp.Hash, false,
ro.queryOptions...)
if filter == nil {
if err != nil {
return nil, fmt.Errorf("Couldn't get basic filter for "+
"block %d (%s)", curStamp.Height, curStamp.Hash)
}
matched, err := filter.MatchAny(builder.DeriveKey(
&curStamp.Hash), watchList)
matched := false
if filter != nil {
matched, err = filter.MatchAny(builder.DeriveKey(
&curStamp.Hash), watchList)
}
if err != nil {
return nil, err
}
if !matched {
filter = s.GetCFilter(curStamp.Hash, true,
filter, err = s.GetCFilter(curStamp.Hash, true,
ro.queryOptions...)
if filter == nil {
if err != nil {
return nil, fmt.Errorf("Couldn't get extended "+
"filter for block %d (%s)",
curStamp.Height, curStamp.Hash)
}
matched, err = filter.MatchAny(builder.DeriveKey(
&curStamp.Hash), watchList)
if filter != nil {
matched, err = filter.MatchAny(
builder.DeriveKey(&curStamp.Hash),
watchList)
}
}
// If either is matched, download the block and check to see
// what we have.
if matched {
block := s.GetBlockFromNetwork(curStamp.Hash,
block, err := s.GetBlockFromNetwork(curStamp.Hash,
ro.queryOptions...)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("Couldn't get "+
"block %d (%s)",
curStamp.Height, curStamp.Hash)
return nil, fmt.Errorf("Couldn't get block %d "+
"(%s)", curStamp.Height, curStamp.Hash)
}
// If we've spent the output in this block, return an
// error stating that the output is spent.
Expand Down
Loading

0 comments on commit 5427079

Please sign in to comment.