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: Problems / still to do:
* Only connects to one node, and that node is hard-coded. * 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. * Re-orgs affect only headers, and don't evict confirmed transactions.
* Double spends are not detected; Double spent txs will stay at height 0. * Double spends are not detected; Double spent txs will stay at height 0.
* Tx creation and signing is still very rudimentary. * Tx creation and signing is still very rudimentary.
* There may be wire-protocol irregularities which can get it kicked off. * There may be wire-protocol irregularities which can get it kicked off.
Hopefully I can get most of that list deleted soon. 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 mBlockQueue chan HashAndHeight
// fPositives is a channel to keep track of bloom filter false positives. // fPositives is a channel to keep track of bloom filter false positives.
fPositives chan int32 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, func OpenSPV(remoteNode string, hfn, tsfn string,
@ -134,6 +138,7 @@ func OpenSPV(remoteNode string, hfn, tsfn string,
go s.outgoingMessageHandler() go s.outgoingMessageHandler()
s.mBlockQueue = make(chan HashAndHeight, 32) // queue depth 32 is a thing s.mBlockQueue = make(chan HashAndHeight, 32) // queue depth 32 is a thing
s.fPositives = make(chan int32, 4000) // a block full, approx s.fPositives = make(chan int32, 4000) // a block full, approx
s.inWaitState = make(chan bool)
go s.fPositiveHandler() go s.fPositiveHandler()
return s, nil 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()) return fmt.Errorf("Txid store error: %s\n", err.Error())
} }
} }
err = s.TS.SetDBSyncHeight(hah.height) err = s.TS.SetDBSyncHeight(hah.height)
if err != nil { if err != nil {
return err 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 return nil
} }
@ -385,7 +395,6 @@ func (s *SPVCon) IngestHeaders(m *wire.MsgHeaders) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
// advance chain tip // advance chain tip
tip++ tip++
// check last header // check last header
@ -408,7 +417,6 @@ func (s *SPVCon) IngestHeaders(m *wire.MsgHeaders) (bool, error) {
} }
} }
log.Printf("Headers to height %d OK.", tip) log.Printf("Headers to height %d OK.", tip)
return true, nil return true, nil
} }

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

@ -5,6 +5,8 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -238,7 +240,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx) (uint32, error) {
var spentOPs [][]byte var spentOPs [][]byte
var nUtxoBytes [][]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() inTxid := tx.TxSha()
height, ok := ts.OKTxids[inTxid] height, ok := ts.OKTxids[inTxid]
if !ok { if !ok {
@ -246,6 +248,16 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx) (uint32, error) {
inTxid.String()) 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 // before entering into db, serialize all inputs of the ingested tx
for _, txin := range tx.TxIn { for _, txin := range tx.TxIn {
nOP, err := outPointToBytes(&txin.PreviousOutPoint) nOP, err := outPointToBytes(&txin.PreviousOutPoint)