add hard mode test methods

really simple to check block merkle root
next can modify "ingestMerkleBlock" to ingest either type of block.
This commit is contained in:
Tadge Dryja 2016-02-06 19:15:35 -08:00
parent 63b5926e01
commit 8518be13f7
6 changed files with 176 additions and 30 deletions

View File

@ -151,7 +151,13 @@ func Shellparse(cmdslice []string) error {
}
return nil
}
if cmd == "blk" {
err = Blk(args)
if err != nil {
fmt.Printf("blk error: %s\n", err)
}
return nil
}
fmt.Printf("Command not recognized. type help for command list.\n")
return nil
}
@ -167,6 +173,26 @@ func Txs(args []string) error {
return nil
}
func Blk(args []string) error {
if SCon.RBytes == 0 {
return fmt.Errorf("Can't check block, spv connection broken")
}
if len(args) == 0 {
return fmt.Errorf("must specify height")
}
height, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
return err
}
// request most recent block just to test
err = SCon.AskForOneBlock(int32(height))
if err != nil {
return err
}
return nil
}
// Bal prints out your score.
func Bal(args []string) error {
if SCon.TS == nil {
@ -208,6 +234,9 @@ func Adr(args []string) error {
// Send sends coins.
func Send(args []string) error {
if SCon.RBytes == 0 {
return fmt.Errorf("Can't send, spv connection broken")
}
// get all utxos from the database
allUtxos, err := SCon.TS.GetAllUtxos()
if err != nil {

View File

@ -21,6 +21,11 @@ const (
type SPVCon struct {
con net.Conn // the (probably tcp) connection to the node
// Enhanced SPV modes for users who have outgrown easy mode SPV
// but have not yet graduated to full nodes.
HardMode bool // hard mode doesn't use filters.
Ironman bool // ironman only gets blocks, never requests txs.
headerMutex sync.Mutex
headerFile *os.File // file for SPV headers
@ -90,30 +95,34 @@ func (s *SPVCon) RemoveHeaders(r int32) error {
return nil
}
func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) error {
func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) {
txids, err := checkMBlock(m) // check self-consistency
if err != nil {
return err
log.Printf("Merkle block error: %s\n", err.Error())
return
}
var hah HashAndHeight
select { // select here so we don't block on an unrequested mblock
case hah = <-s.mBlockQueue: // pop height off mblock queue
break
default:
return fmt.Errorf("Unrequested merkle block")
log.Printf("Unrequested merkle block")
return
}
// this verifies order, and also that the returned header fits
// into our SPV header file
newMerkBlockSha := m.Header.BlockSha()
if !hah.blockhash.IsEqual(&newMerkBlockSha) {
return fmt.Errorf("merkle block out of order error")
log.Printf("merkle block out of order error")
return
}
for _, txid := range txids {
err := s.TS.AddTxid(txid, hah.height)
if err != nil {
return fmt.Errorf("Txid store error: %s\n", err.Error())
log.Printf("Txid store error: %s\n", err.Error())
return
}
}
// write to db that we've sync'd to the height indicated in the
@ -121,7 +130,8 @@ func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) error {
// the txs yet but if there are problems with the txs we should backtrack.
err = s.TS.SetDBSyncHeight(hah.height)
if err != nil {
return err
log.Printf("Merkle block error: %s\n", err.Error())
return
}
if hah.final {
// don't set waitstate; instead, ask for headers again!
@ -130,11 +140,11 @@ func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock) error {
// that way you are pretty sure you're synced up.
err = s.AskForHeaders()
if err != nil {
return err
log.Printf("Merkle block error: %s\n", err.Error())
return
}
}
return nil
return
}
// IngestHeaders takes in a bunch of headers and appends them to the

84
uspv/hardmode.go Normal file
View File

@ -0,0 +1,84 @@
package uspv
import (
"fmt"
"log"
"os"
"github.com/btcsuite/btcd/wire"
)
// BlockRootOK checks that all the txs in the block match the merkle root.
// Only checks merkle root; it doesn't look at txs themselves.
func BlockRootOK(blk wire.MsgBlock) bool {
fmt.Printf("BlockRootOK for block %s\n", blk.BlockSha().String())
var shas []*wire.ShaHash
for _, tx := range blk.Transactions { // make slice of txids
nSha := tx.TxSha()
shas = append(shas, &nSha)
}
// pad out tx slice to get the full tree base
neededLen := int(nextPowerOfTwo(uint32(len(shas)))) // kindof ugly
for len(shas) < neededLen {
shas = append(shas, nil)
}
fmt.Printf("Padded %d txs in block to %d\n",
len(blk.Transactions), len(shas))
// calculate merkle root. Terse, eh?
for len(shas) > 1 {
shas = append(shas[2:], MakeMerkleParent(shas[0], shas[1]))
}
fmt.Printf("calc'd mroot %s, %s in header\n",
shas[0].String(), blk.Header.MerkleRoot.String())
if blk.Header.MerkleRoot.IsEqual(shas[0]) {
return true
}
return false
}
func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
ok := BlockRootOK(*m) // check self-consistency
if !ok {
fmt.Printf("block %s not OK!!11\n", m.BlockSha().String())
return
}
return
}
func (s *SPVCon) AskForOneBlock(h int32) error {
var hdr wire.BlockHeader
var err error
dbTip := int32(h)
s.headerMutex.Lock() // seek to header we need
_, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET)
if err != nil {
return err
}
err = hdr.Deserialize(s.headerFile) // read header, done w/ file for now
s.headerMutex.Unlock() // unlock after reading 1 header
if err != nil {
log.Printf("header deserialize error!\n")
return err
}
bHash := hdr.BlockSha()
// create inventory we're asking for
iv1 := wire.NewInvVect(wire.InvTypeBlock, &bHash)
gdataMsg := wire.NewMsgGetData()
// add inventory
err = gdataMsg.AddInvVect(iv1)
if err != nil {
return err
}
s.outMsgQueue <- gdataMsg
return nil
}

View File

@ -7,25 +7,24 @@ import (
)
func MakeMerkleParent(left *wire.ShaHash, right *wire.ShaHash) *wire.ShaHash {
// this can screw things up; CVE-2012-2459
// dupes can screw things up; CVE-2012-2459. check for them
if left != nil && right != nil && left.IsEqual(right) {
fmt.Printf("DUP HASH CRASH")
return nil
}
// if left chils is nil, output nil. Shouldn't need this?
// if left child is nil, output nil. Need this for hard mode.
if left == nil {
fmt.Printf("L CRASH")
return nil
}
// if right is nil, has left with itself
// if right is nil, hash left with itself
if right == nil {
right = left
}
// Concatenate the left and right nodes
var sha [wire.HashSize * 2]byte
copy(sha[:wire.HashSize], left[:])
copy(sha[wire.HashSize:], right[:])
var sha [64]byte
copy(sha[:32], left[:])
copy(sha[32:], right[:])
newSha := wire.DoubleSha256SH(sha[:])
return &newSha

View File

@ -31,11 +31,7 @@ func (s *SPVCon) incomingMessageHandler() {
case *wire.MsgPong:
log.Printf("Got a pong response. OK.\n")
case *wire.MsgMerkleBlock:
err = s.IngestMerkleBlock(m)
if err != nil {
log.Printf("Merkle block error: %s\n", err.Error())
continue
}
s.IngestMerkleBlock(m)
case *wire.MsgHeaders: // concurrent because we keep asking for blocks
go s.HeaderHandler(m)
case *wire.MsgTx: // not concurrent! txs must be in order
@ -52,6 +48,8 @@ func (s *SPVCon) incomingMessageHandler() {
}
case *wire.MsgGetData:
s.GetDataHandler(m)
case *wire.MsgBlock:
s.IngestBlock(m)
default:
log.Printf("Got unknown message type %s\n", m.Command())
}
@ -116,10 +114,14 @@ func (s *SPVCon) HeaderHandler(m *wire.MsgHeaders) {
return
}
// no moar, done w/ headers, get merkleblocks
err = s.AskForMerkBlocks()
if err != nil {
log.Printf("AskForMerkBlocks error: %s", err.Error())
return
if s.HardMode { // in hard mode ask for regular blocks.
} else {
err = s.AskForMerkBlocks()
if err != nil {
log.Printf("AskForMerkBlocks error: %s", err.Error())
return
}
}
}
@ -133,6 +135,25 @@ func (s *SPVCon) TxHandler(m *wire.MsgTx) {
log.Printf("Tx %s unknown, will not ingest\n")
return
}
// check for double spends
allTxs, err := s.TS.GetAllTxs()
if err != nil {
log.Printf("Can't get txs from db: %s", err.Error())
return
}
dubs, err := CheckDoubleSpends(m, allTxs)
if err != nil {
log.Printf("CheckDoubleSpends error: %s", err.Error())
return
}
if len(dubs) > 0 {
for i, dub := range dubs {
fmt.Printf("dub %d known tx %s and new tx %s are exclusive!!!\n",
i, dub.String(), m.TxSha().String())
}
}
hits, err := s.TS.Ingest(m, height)
if err != nil {
log.Printf("Incoming Tx error: %s\n", err.Error())
@ -176,9 +197,12 @@ func (s *SPVCon) InvHandler(m *wire.MsgInv) {
for i, thing := range m.InvList {
log.Printf("\t%d)%s : %s",
i, thing.Type.String(), thing.Hash.String())
if thing.Type == wire.InvTypeTx { // new tx, OK it at 0 and request
s.TS.AddTxid(&thing.Hash, 0) // unconfirmed
s.AskForTx(thing.Hash)
if thing.Type == wire.InvTypeTx {
if !s.Ironman { // ignore tx invs in ironman mode
// new tx, OK it at 0 and request
s.TS.AddTxid(&thing.Hash, 0) // unconfirmed
s.AskForTx(thing.Hash)
}
}
if thing.Type == wire.InvTypeBlock { // new block what to do?
select {

View File

@ -103,7 +103,7 @@ func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
// GetDoubleSpends takes a transaction and compares it with
// all transactions in the db. It returns a slice of all txids in the db
// which are double spent by the received tx.
func GetDoubleSpends(
func CheckDoubleSpends(
argTx *wire.MsgTx, txs []*wire.MsgTx) ([]*wire.ShaHash, error) {
var dubs []*wire.ShaHash // slice of all double-spent txs