github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/chain/chain_tree.go (about)

     1  package chain
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"sort"
     7  	"time"
     8  
     9  	"github.com/piotrnar/gocoin/lib/btc"
    10  	"github.com/piotrnar/gocoin/lib/others/sys"
    11  )
    12  
    13  type BlockTreeNode struct {
    14  	BlockHash *btc.Uint256
    15  	Height    uint32
    16  	Parent    *BlockTreeNode
    17  	Childs    []*BlockTreeNode
    18  
    19  	BlockSize  uint32 // if this is zero, only header is known so far
    20  	TxCount    uint32
    21  	SigopsCost uint32
    22  
    23  	BlockHeader [80]byte
    24  
    25  	Trusted sys.SyncBool
    26  }
    27  
    28  func (ch *Chain) ParseTillBlock(end *BlockTreeNode) {
    29  	var crec *BlckCachRec
    30  	var er error
    31  	var trusted bool
    32  	var tot_bytes uint64
    33  
    34  	last := ch.LastBlock()
    35  	var total_size_to_process uint64
    36  	fmt.Print("Calculating size of blockchain overhead...")
    37  	for n := end; n != nil && n != last; n = n.Parent {
    38  		l, _ := ch.Blocks.BlockLength(n.BlockHash, false)
    39  		total_size_to_process += uint64(l)
    40  	}
    41  	fmt.Println("\rApplying", total_size_to_process>>20, "MB of transactions data from", end.Height-last.Height, "blocks to UTXO.db")
    42  	sta := time.Now()
    43  	prv := sta
    44  	for !AbortNow && last != end {
    45  		cur := time.Now()
    46  		if cur.Sub(prv) >= 10*time.Second {
    47  			mbps := float64(tot_bytes) / float64(cur.Sub(sta)/1e3)
    48  			sec_left := int64(float64(total_size_to_process) / 1e6 / mbps)
    49  			fmt.Printf("ParseTillBlock %d / %d ... %.2f MB/s - %d:%02d:%02d left (%d)\n", last.Height,
    50  				end.Height, mbps, sec_left/3600, (sec_left/60)%60, sec_left%60, cur.Unix()-sta.Unix())
    51  			prv = cur
    52  		}
    53  
    54  		nxt := last.FindPathTo(end)
    55  		if nxt == nil {
    56  			break
    57  		}
    58  
    59  		if nxt.BlockSize == 0 {
    60  			println("ParseTillBlock: ", nxt.Height, nxt.BlockHash.String(), "- not yet commited")
    61  			break
    62  		}
    63  
    64  		crec, trusted, er = ch.Blocks.BlockGetInternal(nxt.BlockHash, true)
    65  		if er != nil {
    66  			panic("Db.BlockGet(): " + er.Error())
    67  		}
    68  		tot_bytes += uint64(len(crec.Data))
    69  		l, _ := ch.Blocks.BlockLength(nxt.BlockHash, false)
    70  		total_size_to_process -= uint64(l)
    71  
    72  		bl, er := btc.NewBlock(crec.Data)
    73  		if er != nil {
    74  			ch.DeleteBranch(nxt, nil)
    75  			break
    76  		}
    77  		bl.Height = nxt.Height
    78  
    79  		// Recover the flags to be used when verifying scripts for non-trusted blocks (stored orphaned blocks)
    80  		ch.ApplyBlockFlags(bl)
    81  
    82  		// Do not recover MedianPastTime as it is only checked in PostCheckBlock()
    83  		// that had to be done before the block was stored on disk.
    84  
    85  		er = bl.BuildTxList()
    86  		if er != nil {
    87  			ch.DeleteBranch(nxt, nil)
    88  			break
    89  		}
    90  
    91  		bl.Trusted.Store(trusted)
    92  
    93  		changes, sigopscost, er := ch.ProcessBlockTransactions(bl, nxt.Height, end.Height)
    94  		if er != nil {
    95  			println("ProcessBlockTransactionsB", nxt.BlockHash.String(), nxt.Height, er.Error())
    96  			ch.DeleteBranch(nxt, nil)
    97  			break
    98  		}
    99  		nxt.SigopsCost = sigopscost
   100  		if !trusted {
   101  			ch.Blocks.BlockTrusted(bl.Hash.Hash[:])
   102  		}
   103  
   104  		ch.Unspent.CommitBlockTxs(changes, bl.Hash.Hash[:])
   105  
   106  		ch.SetLast(nxt)
   107  		last = nxt
   108  
   109  		if ch.CB.BlockMinedCB != nil {
   110  			bl.Height = nxt.Height
   111  			bl.LastKnownHeight = end.Height
   112  			ch.CB.BlockMinedCB(bl)
   113  		}
   114  	}
   115  
   116  	if !AbortNow && last != end {
   117  		end, _ = ch.BlockTreeRoot.FindFarthestNode()
   118  		fmt.Println("ParseTillBlock failed - now go to", end.Height)
   119  		ch.MoveToBlock(end)
   120  	}
   121  }
   122  
   123  func (n *BlockTreeNode) BlockVersion() uint32 {
   124  	return binary.LittleEndian.Uint32(n.BlockHeader[0:4])
   125  }
   126  
   127  func (n *BlockTreeNode) Timestamp() uint32 {
   128  	return binary.LittleEndian.Uint32(n.BlockHeader[68:72])
   129  }
   130  
   131  func (n *BlockTreeNode) Bits() uint32 {
   132  	return binary.LittleEndian.Uint32(n.BlockHeader[72:76])
   133  }
   134  
   135  // GetMedianTimePast returns the median time of the last 11 blocks.
   136  func (pindex *BlockTreeNode) GetMedianTimePast() uint32 {
   137  	var pmedian [MedianTimeSpan]int
   138  	pbegin := MedianTimeSpan
   139  	pend := MedianTimeSpan
   140  	for i := 0; i < MedianTimeSpan && pindex != nil; i++ {
   141  		pbegin--
   142  		pmedian[pbegin] = int(pindex.Timestamp())
   143  		pindex = pindex.Parent
   144  	}
   145  	sort.Ints(pmedian[pbegin:pend])
   146  	return uint32(pmedian[pbegin+((pend-pbegin)/2)])
   147  }
   148  
   149  // FindFarthestNode looks for the farthest node.
   150  func (n *BlockTreeNode) FindFarthestNode() (*BlockTreeNode, int) {
   151  	//fmt.Println("FFN:", n.Height, "kids:", len(n.Childs))
   152  	if len(n.Childs) == 0 {
   153  		return n, 0
   154  	}
   155  	res, depth := n.Childs[0].FindFarthestNode()
   156  	if len(n.Childs) > 1 {
   157  		for i := 1; i < len(n.Childs); i++ {
   158  			_re, _dept := n.Childs[i].FindFarthestNode()
   159  			if _dept > depth {
   160  				res = _re
   161  				depth = _dept
   162  			}
   163  		}
   164  	}
   165  	return res, depth + 1
   166  }
   167  
   168  // FindPathTo returns the next node that leads to the given destination.
   169  func (n *BlockTreeNode) FindPathTo(end *BlockTreeNode) *BlockTreeNode {
   170  	if n == end {
   171  		return nil
   172  	}
   173  
   174  	if end.Height <= n.Height {
   175  		panic("FindPathTo: End block is not higher then current")
   176  	}
   177  
   178  	if len(n.Childs) == 0 {
   179  		panic("FindPathTo: Unknown path to block " + end.BlockHash.String())
   180  	}
   181  
   182  	if len(n.Childs) == 1 {
   183  		return n.Childs[0] // if there is only one child, do it fast
   184  	}
   185  
   186  	for {
   187  		// more then one children: go from the end until you reach the current node
   188  		if end.Parent == n {
   189  			return end
   190  		}
   191  		end = end.Parent
   192  	}
   193  }
   194  
   195  // HasAllParents checks whether the given node has all its parent blocks already comitted.
   196  func (ch *Chain) HasAllParents(dst *BlockTreeNode) bool {
   197  	for {
   198  		dst = dst.Parent
   199  		if ch.OnActiveBranch(dst) {
   200  			return true
   201  		}
   202  		if dst == nil || dst.TxCount == 0 {
   203  			return false
   204  		}
   205  	}
   206  }
   207  
   208  // OnActiveBranch returns true if the given node is on the active branch.
   209  func (ch *Chain) OnActiveBranch(dst *BlockTreeNode) bool {
   210  	top := ch.LastBlock()
   211  	for {
   212  		if dst == top {
   213  			return true
   214  		}
   215  		if dst.Height >= top.Height {
   216  			return false
   217  		}
   218  		top = top.Parent
   219  	}
   220  }
   221  
   222  // MoveToBlock performs a channel reorg.
   223  func (ch *Chain) MoveToBlock(dst *BlockTreeNode) {
   224  	cur := dst
   225  	lastblock := ch.LastBlock()
   226  	for cur.Height > lastblock.Height {
   227  		cur = cur.Parent
   228  		// if cur.TxCount is zero, it means we dont yet have this block's data
   229  		if cur.TxCount == 0 {
   230  			fmt.Println("MoveToBlock cannot continue A1")
   231  			fmt.Println("Trying to go:", dst.BlockHash.String(), dst.Height)
   232  			fmt.Println("Cannot go at:", cur.BlockHash.String(), cur.Height)
   233  			return
   234  		}
   235  	}
   236  
   237  	for lastblock.Height > cur.Height { // this is a rare case when the new branch has less blocks but more POW
   238  		lastblock = lastblock.Parent
   239  		// if cur.TxCount is zero, it means we dont yet have this block's data
   240  		if lastblock.TxCount == 0 {
   241  			fmt.Println("MoveToBlock cannot continue A2")
   242  			fmt.Println("Trying to go:", dst.BlockHash.String(), dst.Height)
   243  			fmt.Println("Cannot go at:", cur.BlockHash.String(), cur.Height)
   244  			return
   245  		}
   246  	}
   247  
   248  	// At this point both "ch.blockTreeEnd" and "cur" should be at the same height
   249  	for tmp := lastblock; tmp != cur; tmp = tmp.Parent {
   250  		if cur.Parent.TxCount == 0 {
   251  			fmt.Println("MoveToBlock cannot continue B")
   252  			fmt.Println("Trying to go:", dst.BlockHash.String(), dst.Height)
   253  			fmt.Println("Cannot go at:", cur.Parent.BlockHash.String(), cur.Parent.Height)
   254  			return
   255  		}
   256  		cur = cur.Parent
   257  	}
   258  
   259  	// At this point "cur" is at the highest common block
   260  	for ch.LastBlock() != cur {
   261  		if AbortNow {
   262  			return
   263  		}
   264  		ch.UndoLastBlock()
   265  	}
   266  	ch.ParseTillBlock(dst)
   267  }
   268  
   269  func (ch *Chain) UndoLastBlock() {
   270  	last := ch.LastBlock()
   271  	fmt.Println("Undo block", last.Height, last.BlockHash.String(), last.BlockSize>>10, "KB")
   272  
   273  	crec, _, er := ch.Blocks.BlockGetInternal(last.BlockHash, true)
   274  	if er != nil {
   275  		panic(er.Error())
   276  	}
   277  
   278  	bl, er := btc.NewBlock(crec.Data)
   279  	if er != nil {
   280  		panic("UndoLastBlock: NewBlock() should not fail with block from disk")
   281  	}
   282  
   283  	er = bl.BuildTxList()
   284  	if er != nil {
   285  		panic("UndoLastBlock: BuildTxList() should not fail with block from disk")
   286  	}
   287  
   288  	ch.Unspent.UndoBlockTxs(bl, last.Parent.BlockHash.Hash[:])
   289  	if ch.CB.BlockUndoneCB != nil {
   290  		ch.CB.BlockUndoneCB(bl)
   291  	}
   292  	ch.SetLast(last.Parent)
   293  }
   294  
   295  // make sure ch.BlockIndexAccess is locked before calling it
   296  func (cur *BlockTreeNode) delAllChildren(ch *Chain, deleteCallback func(*btc.Uint256)) {
   297  	for i := range cur.Childs {
   298  		if deleteCallback != nil {
   299  			deleteCallback(cur.Childs[i].BlockHash)
   300  		}
   301  		cur.Childs[i].delAllChildren(ch, deleteCallback)
   302  		delete(ch.BlockIndex, cur.Childs[i].BlockHash.BIdx())
   303  		ch.Blocks.BlockInvalid(cur.BlockHash.Hash[:])
   304  	}
   305  	cur.Childs = nil
   306  }
   307  
   308  func (ch *Chain) DeleteBranch(cur *BlockTreeNode, deleteCallback func(*btc.Uint256)) {
   309  	// first disconnect it from the Parent
   310  	ch.Blocks.BlockInvalid(cur.BlockHash.Hash[:])
   311  	ch.BlockIndexAccess.Lock()
   312  	delete(ch.BlockIndex, cur.BlockHash.BIdx())
   313  	cur.Parent.delChild(cur)
   314  	cur.delAllChildren(ch, deleteCallback)
   315  	ch.BlockIndexAccess.Unlock()
   316  }
   317  
   318  func (n *BlockTreeNode) addChild(c *BlockTreeNode) {
   319  	n.Childs = append(n.Childs, c)
   320  }
   321  
   322  func (n *BlockTreeNode) delChild(c *BlockTreeNode) {
   323  	newChds := make([]*BlockTreeNode, len(n.Childs)-1)
   324  	xxx := 0
   325  	for i := range n.Childs {
   326  		if n.Childs[i] != c {
   327  			newChds[xxx] = n.Childs[i]
   328  			xxx++
   329  		}
   330  	}
   331  	if xxx != len(n.Childs)-1 {
   332  		panic("Child not found")
   333  	}
   334  	n.Childs = newChds
   335  }