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  }