diff --git a/shell.go b/shell.go index 21dd499a..a9a57cda 100644 --- a/shell.go +++ b/shell.go @@ -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 { diff --git a/uspv/eight333.go b/uspv/eight333.go index 1c904476..5246ae9d 100644 --- a/uspv/eight333.go +++ b/uspv/eight333.go @@ -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 diff --git a/uspv/hardmode.go b/uspv/hardmode.go new file mode 100644 index 00000000..92d07e12 --- /dev/null +++ b/uspv/hardmode.go @@ -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 +} diff --git a/uspv/mblock.go b/uspv/mblock.go index 823ec805..c67cf81d 100644 --- a/uspv/mblock.go +++ b/uspv/mblock.go @@ -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 diff --git a/uspv/msghandler.go b/uspv/msghandler.go index 7da5be0c..1336a02b 100644 --- a/uspv/msghandler.go +++ b/uspv/msghandler.go @@ -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 { diff --git a/uspv/txstore.go b/uspv/txstore.go index b5596ef7..17282bfb 100644 --- a/uspv/txstore.go +++ b/uspv/txstore.go @@ -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