fix readme, better state machine for sync
This commit is contained in:
parent
4513d77216
commit
a9cf239ec3
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user