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 }