lnd.xprv/uspv/hardmode.go
Olaoluwa Osuntokun fcff17c336
multi: change all imports to roasbeef's forks
This commit will allow the general public to build lnd without jumping
through hoops setting up their local git branches nicely with all of
our forks.
2016-05-15 17:22:37 +03:00

232 lines
7.1 KiB
Go

package uspv
import (
"bytes"
"fmt"
"log"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
"github.com/roasbeef/btcutil/bloom"
)
var (
WitMagicBytes = []byte{0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed}
)
// BlockRootOK checks for block self-consistency.
// If the block has no wintess txs, and no coinbase witness commitment,
// it only checks the tx merkle root. If either a witness commitment or
// any witnesses are detected, it also checks that as well.
// Returns false if anything goes wrong, true if everything is fine.
func BlockOK(blk wire.MsgBlock) bool {
var txids, wtxids []*wire.ShaHash // txids and wtxids
// witMode true if any tx has a wintess OR coinbase has wit commit
var witMode bool
for _, tx := range blk.Transactions { // make slice of (w)/txids
txid := tx.TxSha()
wtxid := tx.WTxSha()
if !witMode && !txid.IsEqual(&wtxid) {
witMode = true
}
txids = append(txids, &txid)
wtxids = append(wtxids, &wtxid)
}
var commitBytes []byte
// try to extract coinbase witness commitment (even if !witMode)
cb := blk.Transactions[0] // get coinbase tx
for i := len(cb.TxOut) - 1; i >= 0; i-- { // start at the last txout
if bytes.HasPrefix(cb.TxOut[i].PkScript, WitMagicBytes) &&
len(cb.TxOut[i].PkScript) > 37 {
// 38 bytes or more, and starts with WitMagicBytes is a hit
commitBytes = cb.TxOut[i].PkScript[6:38]
witMode = true // it there is a wit commit it must be valid
}
}
if witMode { // witmode, so check witness tree
// first find ways witMode can be disqualified
if len(commitBytes) != 32 {
// witness in block but didn't find a wintess commitment; fail
log.Printf("block %s has witness but no witcommit",
blk.BlockSha().String())
return false
}
if len(cb.TxIn) != 1 {
log.Printf("block %s coinbase tx has %d txins (must be 1)",
blk.BlockSha().String(), len(cb.TxIn))
return false
}
if len(cb.TxIn[0].Witness) != 1 {
log.Printf("block %s coinbase has %d witnesses (must be 1)",
blk.BlockSha().String(), len(cb.TxIn[0].Witness))
return false
}
if len(cb.TxIn[0].Witness[0]) != 32 {
log.Printf("block %s coinbase has %d byte witness nonce (not 32)",
blk.BlockSha().String(), len(cb.TxIn[0].Witness[0]))
return false
}
// witness nonce is the cb's witness, subject to above constraints
witNonce, err := wire.NewShaHash(cb.TxIn[0].Witness[0])
if err != nil {
log.Printf("Witness nonce error: %s", err.Error())
return false // not sure why that'd happen but fail
}
var empty [32]byte
wtxids[0].SetBytes(empty[:]) // coinbase wtxid is 0x00...00
// witness root calculated from wtixds
witRoot := calcRoot(wtxids)
calcWitCommit := wire.DoubleSha256SH(
append(witRoot.Bytes(), witNonce.Bytes()...))
// witness root given in coinbase op_return
givenWitCommit, err := wire.NewShaHash(commitBytes)
if err != nil {
log.Printf("Witness root error: %s", err.Error())
return false // not sure why that'd happen but fail
}
// they should be the same. If not, fail.
if !calcWitCommit.IsEqual(givenWitCommit) {
log.Printf("Block %s witRoot error: calc %s given %s",
blk.BlockSha().String(),
calcWitCommit.String(), givenWitCommit.String())
return false
}
}
// got through witMode check so that should be OK;
// check regular txid merkleroot. Which is, like, trivial.
return blk.Header.MerkleRoot.IsEqual(calcRoot(txids))
}
// calcRoot calculates the merkle root of a slice of hashes.
func calcRoot(hashes []*wire.ShaHash) *wire.ShaHash {
for len(hashes) < int(nextPowerOfTwo(uint32(len(hashes)))) {
hashes = append(hashes, nil) // pad out hash slice to get the full base
}
for len(hashes) > 1 { // calculate merkle root. Terse, eh?
hashes = append(hashes[2:], MakeMerkleParent(hashes[0], hashes[1]))
}
return hashes[0]
}
func (ts *TxStore) Refilter() error {
allUtxos, err := ts.GetAllUtxos()
if err != nil {
return err
}
filterElements := uint32(len(allUtxos) + len(ts.Adrs))
ts.localFilter = bloom.NewFilter(filterElements, 0, 0, wire.BloomUpdateAll)
for _, u := range allUtxos {
ts.localFilter.AddOutPoint(&u.Op)
}
for _, a := range ts.Adrs {
ts.localFilter.Add(a.PkhAdr.ScriptAddress())
}
msg := ts.localFilter.MsgFilterLoad()
fmt.Printf("made %d element filter: %x\n", filterElements, msg.Filter)
return nil
}
// IngestBlock is like IngestMerkleBlock but aralphic
// different enough that it's better to have 2 separate functions
func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
var err error
// var buf bytes.Buffer
// m.SerializeWitness(&buf)
// fmt.Printf("block hex %x\n", buf.Bytes())
// for _, tx := range m.Transactions {
// fmt.Printf("wtxid: %s\n", tx.WTxSha())
// fmt.Printf(" txid: %s\n", tx.TxSha())
// fmt.Printf("%d %s", i, TxToString(tx))
// }
ok := BlockOK(*m) // check block self-consistency
if !ok {
fmt.Printf("block %s not OK!!11\n", m.BlockSha().String())
return
}
var hah HashAndHeight
select { // select here so we don't block on an unrequested mblock
case hah = <-s.blockQueue: // pop height off mblock queue
break
default:
log.Printf("Unrequested full block")
return
}
newBlockSha := m.Header.BlockSha()
if !hah.blockhash.IsEqual(&newBlockSha) {
log.Printf("full block out of order error")
return
}
fPositive := 0 // local filter false positives
reFilter := 10 // after that many false positives, regenerate filter.
// 10? Making it up. False positives have disk i/o cost, and regenning
// the filter also has costs. With a large local filter, false positives
// should be rare.
// iterate through all txs in the block, looking for matches.
// use a local bloom filter to ignore txs that don't affect us
for _, tx := range m.Transactions {
utilTx := btcutil.NewTx(tx)
if s.TS.localFilter.MatchTxAndUpdate(utilTx) {
hits, err := s.TS.Ingest(tx, hah.height)
if err != nil {
log.Printf("Incoming Tx error: %s\n", err.Error())
return
}
if hits > 0 {
// log.Printf("block %d tx %d %s ingested and matches %d utxo/adrs.",
// hah.height, i, tx.TxSha().String(), hits)
} else {
fPositive++ // matched filter but no hits
}
}
}
if fPositive > reFilter {
fmt.Printf("%d filter false positives in this block\n", fPositive)
err = s.TS.Refilter()
if err != nil {
log.Printf("Refilter error: %s\n", err.Error())
return
}
}
// write to db that we've sync'd to the height indicated in the
// merkle block. This isn't QUITE true since we haven't actually gotten
// the txs yet but if there are problems with the txs we should backtrack.
err = s.TS.SetDBSyncHeight(hah.height)
if err != nil {
log.Printf("full block sync error: %s\n", err.Error())
return
}
fmt.Printf("ingested full block %s height %d OK\n",
m.Header.BlockSha().String(), hah.height)
if hah.final { // check sync end
// don't set waitstate; instead, ask for headers again!
// this way the only thing that triggers waitstate is asking for headers,
// getting 0, calling AskForMerkBlocks(), and seeing you don't need any.
// that way you are pretty sure you're synced up.
err = s.AskForHeaders()
if err != nil {
log.Printf("Merkle block error: %s\n", err.Error())
return
}
}
return
}