github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/uspv/hardmode.go (about)

     1  package uspv
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  
     7  	"github.com/mit-dci/lit/btcutil/bloom"
     8  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
     9  	"github.com/mit-dci/lit/lnutil"
    10  	"github.com/mit-dci/lit/logging"
    11  	"github.com/mit-dci/lit/wire"
    12  )
    13  
    14  var (
    15  	// WitMagicBytes ...
    16  	WitMagicBytes = []byte{0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed}
    17  )
    18  
    19  // BlockOK checks for block self-consistency.
    20  // If the block has no wintess txs, and no coinbase witness commitment,
    21  // it only checks the tx merkle root.  If either a witness commitment or
    22  // any witnesses are detected, it also checks that as well.
    23  // Returns false if anything goes wrong, true if everything is fine.
    24  func BlockOK(blk wire.MsgBlock) bool {
    25  	var txids, wtxids []*chainhash.Hash // txids and wtxids
    26  	// witMode true if any tx has a wintess OR coinbase has wit commit
    27  	witMode := false
    28  
    29  	for _, tx := range blk.Transactions { // make slice of (w)/txids
    30  		txid := tx.TxHash()
    31  		txids = append(txids, &txid)
    32  		if !witMode && tx.HasWitness() {
    33  			witMode = true
    34  		}
    35  	}
    36  	if witMode {
    37  		for _, wtx := range blk.Transactions {
    38  			wtxid := wtx.WitnessHash()
    39  			wtxids = append(wtxids, &wtxid)
    40  		}
    41  	}
    42  
    43  	// block minus witnesses should be < 1M
    44  	if blk.SerializeSizeStripped() > 1000000 {
    45  		return false
    46  	}
    47  
    48  	var commitBytes []byte
    49  	// try to extract coinbase witness commitment (even if !witMode)
    50  	cb := blk.Transactions[0]                 // get coinbase tx
    51  	for i := len(cb.TxOut) - 1; i >= 0; i-- { // start at the last txout
    52  		if bytes.HasPrefix(cb.TxOut[i].PkScript, WitMagicBytes) &&
    53  			len(cb.TxOut[i].PkScript) > 37 {
    54  			// 38 bytes or more, and starts with WitMagicBytes is a hit
    55  			commitBytes = cb.TxOut[i].PkScript[6:38]
    56  			witMode = true // it there is a wit commit it must be valid
    57  		}
    58  	}
    59  
    60  	if witMode { // witmode, so check witness tree
    61  		// first find ways witMode can be disqualified
    62  		if len(commitBytes) != 32 {
    63  			// witness in block but didn't find a wintess commitment; fail
    64  			logging.Errorf("block %s has witness but no witcommit",
    65  				blk.BlockHash().String())
    66  			return false
    67  		}
    68  		if len(cb.TxIn) != 1 {
    69  			logging.Errorf("block %s coinbase tx has %d txins (must be 1)",
    70  				blk.BlockHash().String(), len(cb.TxIn))
    71  			return false
    72  		}
    73  		// something weird here with regtest, disable for now
    74  		// The op_return is there but I'm not getting the 0000 witness.
    75  		// maybe because I'm not getting a witness block..?
    76  		/*
    77  			if len(cb.TxIn[0].Witness) != 1 {
    78  				logging.Errorf("block %s coinbase has %d witnesses (must be 1)",
    79  					blk.BlockHash().String(), len(cb.TxIn[0].Witness))
    80  				return false
    81  			}
    82  
    83  			if len(cb.TxIn[0].Witness[0]) != 32 {
    84  				logging.Errorf("block %s coinbase has %d byte witness nonce (not 32)",
    85  					blk.BlockHash().String(), len(cb.TxIn[0].Witness[0]))
    86  				return false
    87  			}
    88  			// witness nonce is the cb's witness, subject to above constraints
    89  			witNonce, err := chainhash.NewHash(cb.TxIn[0].Witness[0])
    90  			if err != nil {
    91  				logging.Errorf("Witness nonce error: %s", err.Error())
    92  				return false // not sure why that'd happen but fail
    93  			}
    94  
    95  			var empty [32]byte
    96  			wtxids[0].SetBytes(empty[:]) // coinbase wtxid is 0x00...00
    97  
    98  			// witness root calculated from wtixds
    99  			witRoot := calcRoot(wtxids)
   100  
   101  			calcWitCommit := chainhash.DoubleHashH(
   102  				append(witRoot.CloneBytes(), witNonce.CloneBytes()...))
   103  
   104  			// witness root given in coinbase op_return
   105  			givenWitCommit, err := chainhash.NewHash(commitBytes)
   106  			if err != nil {
   107  				logging.Errorf("Witness root error: %s", err.Error())
   108  				return false // not sure why that'd happen but fail
   109  			}
   110  			// they should be the same.  If not, fail.
   111  			if !calcWitCommit.IsEqual(givenWitCommit) {
   112  				logging.Errorf("Block %s witRoot error: calc %s given %s",
   113  					blk.BlockHash().String(),
   114  					calcWitCommit.String(), givenWitCommit.String())
   115  				return false
   116  			}
   117  		*/
   118  	}
   119  
   120  	// got through witMode check so that should be OK;
   121  	// check regular txid merkleroot.  Which is, like, trivial.
   122  	return blk.Header.MerkleRoot.IsEqual(calcRoot(txids))
   123  }
   124  
   125  // calcRoot calculates the merkle root of a slice of hashes.
   126  func calcRoot(hashes []*chainhash.Hash) *chainhash.Hash {
   127  	for len(hashes) < int(nextPowerOfTwo(uint32(len(hashes)))) {
   128  		hashes = append(hashes, nil) // pad out hash slice to get the full base
   129  	}
   130  	for len(hashes) > 1 { // calculate merkle root. Terse, eh?
   131  		hashes = append(hashes[2:], MakeMerkleParent(hashes[0], hashes[1]))
   132  	}
   133  	return hashes[0]
   134  }
   135  
   136  // Refilter reconstructs the local in-memory bloom filter.  It does this by
   137  // calling GimmeFilter() but doesn't broadcast the result.
   138  func (s *SPVCon) Refilter(f *bloom.Filter) {
   139  	if !s.HardMode {
   140  		s.SendFilter(f)
   141  	}
   142  }
   143  
   144  var checkedlogblock = false
   145  var logfullblock = true
   146  
   147  func ckLogFullBlock() bool {
   148  	if !checkedlogblock {
   149  		v := os.Getenv("LIT_LOG_INGEST_BLOCK")
   150  		if v == "0" {
   151  			logging.Warnln("Diabling logging of block ingestion.")
   152  			logfullblock = false
   153  		}
   154  		checkedlogblock = true
   155  	}
   156  	return logfullblock
   157  }
   158  
   159  // IngestBlock is like IngestMerkleBlock but aralphic
   160  // different enough that it's better to have 2 separate functions
   161  func (s *SPVCon) IngestBlock(m *wire.MsgBlock) {
   162  	var err error
   163  
   164  	// hand block over via the RawBlockSender chan
   165  	// hopefully this doesn't block
   166  	// ... get it?
   167  	if s.RawBlockActive {
   168  		s.RawBlockSender <- m
   169  	}
   170  
   171  	// This takes care of the whole rawblockactive thing if we just
   172  	// replace the current channel with this new one
   173  	for i := range s.RawBlockDistribute {
   174  		s.RawBlockDistribute[i] <- m
   175  	}
   176  
   177  	ok := BlockOK(*m) // check block self-consistency
   178  	if !ok {
   179  		logging.Errorf("block %s not OK!!11\n", m.BlockHash().String())
   180  		return
   181  	}
   182  
   183  	var hah HashAndHeight
   184  	select { // select here so we don't block on an unrequested mblock
   185  	case hah = <-s.blockQueue: // pop height off mblock queue
   186  		break
   187  	default:
   188  		logging.Errorf("Unrequested full block")
   189  		return
   190  	}
   191  
   192  	newBlockHash := m.Header.BlockHash()
   193  	if !hah.blockhash.IsEqual(&newBlockHash) {
   194  		logging.Errorf("Full block out of order error\n")
   195  		return
   196  	}
   197  
   198  	// iterate through all txs in the block, looking for matches.
   199  	for _, tx := range m.Transactions {
   200  		if s.MatchTx(tx) {
   201  			logging.Infof("found matching tx %s\n", tx.TxHash().String())
   202  			s.TxUpToWallit <- lnutil.TxAndHeight{Tx: tx, Height: hah.height}
   203  		}
   204  	}
   205  
   206  	// tell the channels listening for heights that the height has been reached
   207  	for i := range s.HeightDistribute {
   208  		s.HeightDistribute[i] <- hah.height
   209  	}
   210  
   211  	// tell upper level height has been reached
   212  	s.CurrentHeightChan <- hah.height
   213  	// track our internal height
   214  	s.syncHeight = hah.height
   215  
   216  	if ckLogFullBlock() {
   217  		logging.Debugf("ingested full block %s height %d OK\n",
   218  			m.Header.BlockHash().String(), hah.height)
   219  	}
   220  
   221  	if hah.final { // check sync end
   222  		// don't set waitstate; instead, ask for headers again!
   223  		// this way the only thing that triggers waitstate is asking for headers,
   224  		// getting 0, calling AskForMerkBlocks(), and seeing you don't need any.
   225  		// that way you are pretty sure you're synced up.
   226  		err = s.AskForHeaders()
   227  		if err != nil {
   228  			logging.Errorf("Merkle block error: %s\n", err.Error())
   229  			return
   230  		}
   231  	}
   232  	return
   233  }