fix readme, better state machine for sync

This commit is contained in:
Tadge Dryja 2016-02-02 17:14:13 -08:00
parent 4513d77216
commit a9cf239ec3
4 changed files with 42 additions and 9 deletions

@ -50,10 +50,21 @@ There's still quite a bit left, though most of it hopefully won't be too hard.
Problems / still to do:
* Only connects to one node, and that node is hard-coded.
* Currently doesn't check signatures of received transactions.
* Re-orgs affect only headers, and don't evict confirmed transactions.
* Double spends are not detected; Double spent txs will stay at height 0.
* Tx creation and signing is still very rudimentary.
* There may be wire-protocol irregularities which can get it kicked off.
Hopefully I can get most of that list deleted soon.
(Now sanity checks txs, but can't check sigs... because it's SPV. Right.)
Later functionality to implement:
* "Desktop Mode" SPV, or "Unfiltered" SPV or some other name
This would be a mode where uspv doesn't use bloom filters and request merkle blocks, but instead grabs everything in the block and discards most of the data. This prevents nodes from learning about your utxo set. To further enhance this, it should connect to multiple nodes and relay txs and inv messages to blend in.
* Ironman SPV
Never request txs. Only merkleBlocks (or in above mode, blocks). No unconfirmed transactions are presented to the user, which makes a whole lot of sense as with unconfirmed SPV transactions you're relying completely on the honesty of the reporting node.

@ -50,6 +50,10 @@ type SPVCon struct {
mBlockQueue chan HashAndHeight
// fPositives is a channel to keep track of bloom filter false positives.
fPositives chan int32
// waitState is a channel that is empty while in the header and block
// sync modes, but when in the idle state has a "true" in it.
inWaitState chan bool
}
func OpenSPV(remoteNode string, hfn, tsfn string,
@ -134,6 +138,7 @@ func OpenSPV(remoteNode string, hfn, tsfn string,
go s.outgoingMessageHandler()
s.mBlockQueue = make(chan HashAndHeight, 32) // queue depth 32 is a thing
s.fPositives = make(chan int32, 4000) // a block full, approx
s.inWaitState = make(chan bool)
go s.fPositiveHandler()
return s, nil
@ -317,11 +322,16 @@ func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) error {
return fmt.Errorf("Txid store error: %s\n", err.Error())
}
}
err = s.TS.SetDBSyncHeight(hah.height)
if err != nil {
return err
}
// not super comfortable with this but it seems to work.
if len(s.mBlockQueue) == 0 { // done and fully sync'd
s.inWaitState <- true
}
return nil
}
@ -385,7 +395,6 @@ func (s *SPVCon) IngestHeaders(m *wire.MsgHeaders) (bool, error) {
if err != nil {
return false, err
}
// advance chain tip
tip++
// check last header
@ -408,7 +417,6 @@ func (s *SPVCon) IngestHeaders(m *wire.MsgHeaders) (bool, error) {
}
}
log.Printf("Headers to height %d OK.", tip)
return true, nil
}

@ -45,7 +45,7 @@ func (s *SPVCon) incomingMessageHandler() {
log.Printf("Rejected! cmd: %s code: %s tx: %s reason: %s",
m.Cmd, m.Code.String(), m.Hash.String(), m.Reason)
case *wire.MsgInv:
go s.InvHandler(m)
s.InvHandler(m)
case *wire.MsgNotFound:
log.Printf("Got not found response from remote:")
for i, thing := range m.InvList {
@ -162,12 +162,14 @@ func (s *SPVCon) InvHandler(m *wire.MsgInv) {
s.TS.OKTxids[thing.Hash] = 0 // unconfirmed
s.AskForTx(thing.Hash)
}
if thing.Type == wire.InvTypeBlock { // new block, ingest
if len(s.mBlockQueue) == 0 { // this is not a good check...
// don't ask directly; instead ask for header
if thing.Type == wire.InvTypeBlock { // new block what to do?
select {
case <-s.inWaitState:
// start getting headers
fmt.Printf("asking for headers due to inv block\n")
s.AskForHeaders()
} else {
default:
// drop it as if its component particles had high thermal energies
fmt.Printf("inv block but ignoring, not synched\n")
}
}

@ -5,6 +5,8 @@ import (
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
@ -238,7 +240,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx) (uint32, error) {
var spentOPs [][]byte
var nUtxoBytes [][]byte
// check that we have a height and tx has been SPV OK'd
// first check that we have a height and tx has been SPV OK'd
inTxid := tx.TxSha()
height, ok := ts.OKTxids[inTxid]
if !ok {
@ -246,6 +248,16 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx) (uint32, error) {
inTxid.String())
}
// tx has been OK'd by SPV; check tx sanity
utilTx := btcutil.NewTx(tx) // convert for validation
// checks stuff like inputs >= ouputs
err = blockchain.CheckTransactionSanity(utilTx)
if err != nil {
return hits, err
}
// note that you can't check signatures; this is SPV.
// 0 conf SPV means pretty much nothing. Anyone can say anything.
// before entering into db, serialize all inputs of the ingested tx
for _, txin := range tx.TxIn {
nOP, err := outPointToBytes(&txin.PreviousOutPoint)