github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/uspv/header.go (about) 1 /* this is blockchain technology. Well, except without the blocks. 2 Really it's header chain technology. 3 The blocks themselves don't really make a chain. Just the headers do. 4 */ 5 6 package uspv 7 8 import ( 9 "bytes" 10 "fmt" 11 "io" 12 "math/big" 13 "os" 14 15 "github.com/mit-dci/lit/logging" 16 17 "github.com/mit-dci/lit/btcutil/blockchain" 18 19 "github.com/mit-dci/lit/coinparam" 20 "github.com/mit-dci/lit/wire" 21 ) 22 23 func min(a, b int) int { 24 if a < b { 25 return a 26 } 27 return b 28 } 29 func moreWork(a, b []*wire.BlockHeader, p *coinparam.Params) bool { 30 isMoreWork := false 31 if len(a) == 0 || len(b) == 0 { 32 return false 33 } 34 // if (&a[0].MerkleRoot).IsEqual(&b[0].MerkleRoot) { this always returns false, 35 // so we use the String() method to convert them, thing stole half an hour.. 36 pos := 0 //can safely assume this thanks to the first check 37 for i := min(len(a), len(b)) - 1; i >= 1; i-- { 38 hash := a[i-1].BlockHash() 39 if a[i].PrevBlock.IsEqual(&hash) && b[i].PrevBlock.IsEqual(&hash) { 40 isMoreWork = true 41 pos = i 42 break 43 } 44 } 45 if !isMoreWork { 46 return isMoreWork 47 } else { 48 var a1, b1 []*wire.BlockHeader 49 a1 = a[pos:] 50 b1 = b[pos:] 51 workA := big.NewInt(0) // since raw declarations don't work, lets set it to 0 52 workB := big.NewInt(0) // since raw declarations don't work, lets set it to 0 53 for i := 0; i < len(a1); i++ { 54 workA.Add(blockchain.CalcWork(a1[0].Bits), workA) 55 } 56 for i := 0; i < len(b1); i++ { 57 //logging.Info(i) 58 workB.Add(blockchain.CalcWork(b1[i].Bits), workB) 59 } 60 logging.Info("Work done by alt chains A and B are: ") 61 logging.Info(workA, workB) 62 // due to cmp's stquirks in big, we can't return directly 63 if workA.Cmp(workB) > 0 { // if chain A does more work than B return true 64 return isMoreWork // true 65 } 66 return !isMoreWork // false 67 } 68 } 69 70 // checkProofOfWork verifies the header hashes into something 71 // lower than specified by the 4-byte bits field. 72 func checkProofOfWork(header wire.BlockHeader, p *coinparam.Params, height int32) bool { 73 74 target := blockchain.CompactToBig(header.Bits) 75 76 // The target must more than 0. Why can you even encode negative... 77 if target.Sign() <= 0 { 78 logging.Errorf("block target %064x is neagtive(??)\n", target.Bytes()) 79 return false 80 } 81 // The target must be less than the maximum allowed (difficulty 1) 82 if target.Cmp(p.PowLimit) > 0 { 83 logging.Errorf("block target %064x is "+ 84 "higher than max of %064x", target, p.PowLimit.Bytes()) 85 return false 86 } 87 88 // The header hash must be less than the claimed target in the header. 89 var buf bytes.Buffer 90 _ = wire.WriteBlockHeader(&buf, 0, &header) 91 92 blockHash := p.PoWFunction(buf.Bytes(), height) 93 94 hashNum := new(big.Int) 95 96 hashNum = blockchain.HashToBig(&blockHash) 97 if hashNum.Cmp(target) > 0 { 98 logging.Errorf("block hash %064x is higher than "+ 99 "required target of %064x", hashNum, target) 100 return false 101 } 102 return true 103 } 104 105 // GetHeaderAtHeight gives back a header at the specified height 106 func (s *SPVCon) GetHeaderAtHeight(h int32) (*wire.BlockHeader, error) { 107 s.headerMutex.Lock() 108 defer s.headerMutex.Unlock() 109 110 // height is reduced by startHeight 111 h = h - s.Param.StartHeight 112 113 // seek to that header 114 _, err := s.headerFile.Seek(int64(80*h), os.SEEK_SET) 115 if err != nil { 116 logging.Error(err) 117 return nil, err 118 } 119 120 hdr := new(wire.BlockHeader) 121 err = hdr.Deserialize(s.headerFile) 122 if err != nil { 123 logging.Error(err) 124 return nil, err 125 } 126 return hdr, nil 127 } 128 129 // GetHeaderTipHeight gives back a header at the specified height. 130 func (s *SPVCon) GetHeaderTipHeight() int32 { 131 s.headerMutex.Lock() // start header file ops 132 defer s.headerMutex.Unlock() 133 info, err := s.headerFile.Stat() 134 if err != nil { 135 logging.Errorf("Header file error: %s", err.Error()) 136 return 0 137 } 138 headerFileSize := info.Size() 139 if headerFileSize == 0 || headerFileSize%80 != 0 { // header file broken 140 // try to fix it! 141 s.headerFile.Truncate(headerFileSize - (headerFileSize % 80)) 142 logging.Errorf("ERROR: Header file not a multiple of 80 bytes. Truncating") 143 } 144 // subtract 1 as we want the start of the tip offset, not the end 145 return int32(headerFileSize/80) + s.Param.StartHeight - 1 146 } 147 148 // FindHeader will try to find where the header you give it is. 149 // it runs backwards to find it and gives up after 1000 headers 150 func FindHeader(r io.ReadSeeker, hdr wire.BlockHeader) (int32, error) { 151 152 var cur wire.BlockHeader 153 154 for tries := 1; tries < 2200; tries++ { 155 offset, err := r.Seek(int64(-80*tries), os.SEEK_END) 156 if err != nil { 157 logging.Error(err) 158 return -1, err 159 } 160 161 // for blkhash.IsEqual(&target) { 162 err = cur.Deserialize(r) 163 if err != nil { 164 logging.Error(err) 165 return -1, err 166 } 167 curhash := cur.BlockHash() 168 169 // logging.Infof("try %d %s\n", tries, curhash.String()) 170 171 if hdr.PrevBlock.IsEqual(&curhash) { 172 return int32(offset / 80), nil 173 } 174 } 175 176 return 0, nil 177 } 178 179 // CheckHeaderChain takes in the headers message and sees if they all validate. 180 // This function also needs read access to the previous headers. 181 // Does not deal with re-orgs; assumes new headers link to tip 182 // returns true if *all* headers are cool, false if there is any problem 183 // Note we don't know what the height is, just the relative height. 184 // returnin nil means it worked 185 // returns an int32 usually 0, but if there's a reorg, shows what height to 186 // reorg back to before adding on the headers 187 func CheckHeaderChain( 188 r io.ReadSeeker, inHeaders []*wire.BlockHeader, 189 p *coinparam.Params) (int32, error) { 190 191 // make sure we actually got new headers 192 if len(inHeaders) < 1 { 193 return 0, fmt.Errorf( 194 "CheckHeaderChain: headers message doesn't have any headers.") 195 } 196 197 // first, look through all the incoming headers to make sure 198 // they're at least self-consistent. Do this before even 199 // checking that they link to anything; it's all in-ram and quick 200 for i, hdr := range inHeaders { 201 // check they link to each other 202 // That whole 'blockchain' thing. 203 if i > 1 { 204 hash := inHeaders[i-1].BlockHash() 205 if !hdr.PrevBlock.IsEqual(&hash) { 206 return 0, fmt.Errorf( 207 "headers %d and %d in header message don't link", i, i-1) 208 } 209 } 210 211 // check that header version is non-negative (fork detect) 212 if hdr.Version < 0 { 213 return 0, fmt.Errorf( 214 "header %d in message has negative version (hard fork?)", i) 215 } 216 } 217 // incoming header message is internally consistent, now check that it 218 // links with what we have on disk 219 220 epochLength := int32(p.TargetTimespan / p.TargetTimePerBlock) 221 222 if p.MinHeaders > 0 { 223 epochLength = p.MinHeaders 224 } 225 226 // seek to start of last header 227 pos, err := r.Seek(-80, os.SEEK_END) 228 if err != nil { 229 logging.Error(err) 230 return 0, err 231 } 232 logging.Infof("header file position: %d\n", pos) 233 if pos%80 != 0 { 234 return 0, fmt.Errorf( 235 "CheckHeaderChain: Header file not a multiple of 80 bytes.") 236 } 237 238 // we know incoming height; it's startheight + all the headers on disk + 1 239 height := int32(pos/80) + p.StartHeight + 1 240 241 // see if we don't have enough & load em all. 242 var numheaders int32 // number of headers to read 243 244 // load only last epoch if there are a lot on disk 245 if pos > int64(80*(epochLength+1)) { 246 _, err = r.Seek(int64(-80*(epochLength+1)), os.SEEK_END) 247 numheaders = epochLength + 1 248 } else { // otherwise load everything, start at byte 0 249 _, err = r.Seek(0, os.SEEK_SET) 250 numheaders = height - p.StartHeight 251 } 252 if err != nil { // seems like it will always be ok here..? 253 return 0, err 254 } 255 256 // weird off-by-1 stuff here; makes numheaders, incluing the 0th 257 oldHeaders := make([]*wire.BlockHeader, numheaders) 258 logging.Infof("made %d header slice\n", len(oldHeaders)) 259 // load a bunch of headers from disk into ram 260 for i := range oldHeaders { 261 // read from file at current offset 262 oldHeaders[i] = new(wire.BlockHeader) 263 err = oldHeaders[i].Deserialize(r) 264 if err != nil { 265 logging.Errorf("CheckHeaderChain ran out of file at oldheader %d\n", i) 266 return 0, err 267 } 268 } 269 270 tiphash := oldHeaders[len(oldHeaders)-1].BlockHash() 271 272 var attachHeight int32 273 // make sure the first header in the message points to our on-disk tip 274 if !inHeaders[0].PrevBlock.IsEqual(&tiphash) { 275 276 // find where it points to 277 278 attachHeight, err = FindHeader(r, *inHeaders[0]) 279 if err != nil { 280 return 0, fmt.Errorf( 281 "CheckHeaderChain: header message doesn't attach to tip or anywhere.") 282 } 283 284 // adjust attachHeight by adding the startheight 285 attachHeight += p.StartHeight 286 287 logging.Infof("Header %s attaches at height %d\n", 288 inHeaders[0].BlockHash().String(), attachHeight) 289 290 // if we've been given insufficient headers, don't reorg, but 291 // ask for more headers. 292 293 // Check between two chains with attachHeight+int32(len(inHeaders)) and height 294 // lengths, then Compare the work associated with them. 295 // 1. check whether they really are from the same chain i.e. start from the same Header 296 // 2. Find the most recent common Block 297 // 3. Calculate work on both chains from that block 298 // 4. Return true or false based on which one is better. 299 // the two arrays are chain+inHeaders+attachHeight and the height chain itself 300 // 2,3,4 -? fn 301 302 if moreWork(inHeaders, oldHeaders, p) { 303 // pretty sure this won't work without testing 304 // if attachHeight+int32(len(inHeaders)) < height { 305 return -1, fmt.Errorf( 306 "reorg message up to height %d, but have up to %d", 307 attachHeight+int32(len(inHeaders)), height-1) 308 } 309 310 logging.Infof("reorg from height %d to %d", 311 height-1, attachHeight+int32(len(inHeaders))) 312 313 // reorg is go, snip to attach height 314 reorgDepth := height - attachHeight 315 if reorgDepth > numheaders { 316 return -1, fmt.Errorf("Reorg depth greater than number of headers") 317 } 318 oldHeaders = oldHeaders[:numheaders-reorgDepth] 319 } 320 321 prevHeaders := oldHeaders 322 323 // check difficulty adjustments in the new headers 324 // since we call this many times, append each time 325 for i, hdr := range inHeaders { 326 if height+int32(i) > p.AssumeDiffBefore { 327 // check if there's a valid proof of work. That whole "Bitcoin" thing. 328 if !checkProofOfWork(*hdr, p, height+int32(i)) { 329 logging.Error("header in message has bad proof of work") 330 return 0, fmt.Errorf("header %d in message has bad proof of work", i) 331 } 332 // build slice of "previous" headers 333 prevHeaders = append(prevHeaders, inHeaders[i]) 334 rightBits, err := p.DiffCalcFunction(prevHeaders, height+int32(i), p) 335 if err != nil { 336 logging.Error(err) 337 return 0, fmt.Errorf("Error calculating Block %d %s difficulty. %s", 338 int(height)+i, hdr.BlockHash().String(), err.Error()) 339 } 340 341 // vertcoin diff adjustment not yet implemented 342 // TODO - get rid of coin specific workaround 343 if hdr.Bits != rightBits && (p.Name != "vtctest" && p.Name != "vtc") && !p.TestCoin { 344 return 0, fmt.Errorf("Block %d %s incorrect difficulty. Read %x, expect %x", 345 int(height)+i, hdr.BlockHash().String(), hdr.Bits, rightBits) 346 } 347 } 348 } 349 350 return attachHeight, nil 351 }