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

     1  package chain
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/piotrnar/gocoin/lib/btc"
    11  	"github.com/piotrnar/gocoin/lib/script"
    12  )
    13  
    14  // Make sure to call this function with ch.BlockIndexAccess locked
    15  func (ch *Chain) PreCheckBlock(bl *btc.Block) (dos bool, maybelater bool, er error) {
    16  	// Size limits
    17  	if len(bl.Raw) < 80 {
    18  		er = errors.New("CheckBlock() : size limits failed - RPC_Result:bad-blk-length")
    19  		dos = true
    20  		return
    21  	}
    22  
    23  	ver := bl.Version()
    24  	if ver == 0 {
    25  		er = errors.New("CheckBlock() : Block version 0 not allowed - RPC_Result:bad-version")
    26  		dos = true
    27  		return
    28  	}
    29  
    30  	// Check proof-of-work
    31  	if !btc.CheckProofOfWork(bl.Hash, bl.Bits()) {
    32  		er = errors.New("CheckBlock() : proof of work failed - RPC_Result:high-hash")
    33  		dos = true
    34  		return
    35  	}
    36  
    37  	// Check timestamp (must not be higher than now +2 hours)
    38  	if int64(bl.BlockTime()) > time.Now().Unix()+2*60*60 {
    39  		er = errors.New("CheckBlock() : block timestamp too far in the future - RPC_Result:time-too-new")
    40  		dos = true
    41  		return
    42  	}
    43  
    44  	if prv, pres := ch.BlockIndex[bl.Hash.BIdx()]; pres {
    45  		if prv.Parent == nil {
    46  			// This is genesis block
    47  			er = errors.New("Genesis")
    48  			return
    49  		} else {
    50  			er = errors.New("CheckBlock: " + bl.Hash.String() + " already in - RPC_Result:duplicate")
    51  			return
    52  		}
    53  	}
    54  
    55  	prevblk, ok := ch.BlockIndex[btc.NewUint256(bl.ParentHash()).BIdx()]
    56  	if !ok {
    57  		er = errors.New("CheckBlock: " + bl.Hash.String() + " parent not found - RPC_Result:bad-prevblk")
    58  		maybelater = true
    59  		return
    60  	}
    61  
    62  	bl.Height = prevblk.Height + 1
    63  
    64  	// Reject the block if it reaches into the chain deeper than our unwind buffer
    65  	lst_now := ch.LastBlock()
    66  	if prevblk != lst_now && int(lst_now.Height)-int(bl.Height) >= MovingCheckopintDepth {
    67  		er = errors.New(fmt.Sprint("CheckBlock: btc.Block ", bl.Hash.String(),
    68  			" hooks too deep into the chain: ", bl.Height, "/", lst_now.Height, " ",
    69  			btc.NewUint256(bl.ParentHash()).String(), " - RPC_Result:bad-prevblk"))
    70  		return
    71  	}
    72  
    73  	// Check proof of work
    74  	gnwr := ch.GetNextWorkRequired(prevblk, bl.BlockTime())
    75  	if bl.Bits() != gnwr {
    76  		er = errors.New("CheckBlock: incorrect proof of work - RPC_Result:bad-diffbits")
    77  		dos = true
    78  		return
    79  	}
    80  
    81  	// Check timestamp against prev
    82  	bl.MedianPastTime = prevblk.GetMedianTimePast()
    83  	if bl.BlockTime() <= bl.MedianPastTime {
    84  		er = errors.New("CheckBlock: block's timestamp is too early - RPC_Result:time-too-old")
    85  		dos = true
    86  		return
    87  	}
    88  
    89  	if ver < 2 && bl.Height >= ch.Consensus.BIP34Height ||
    90  		ver < 3 && bl.Height >= ch.Consensus.BIP66Height ||
    91  		ver < 4 && bl.Height >= ch.Consensus.BIP65Height {
    92  		// bad block version
    93  		erstr := fmt.Sprintf("0x%08x", ver)
    94  		er = errors.New("CheckBlock() : Rejected Version=" + erstr + " block - RPC_Result:bad-version(" + erstr + ")")
    95  		dos = true
    96  		return
    97  	}
    98  
    99  	return
   100  }
   101  
   102  func (ch *Chain) GetBlockFlags(block_height uint32, block_time uint32) (flags uint32) {
   103  	if block_time == 0 || block_time >= BIP16SwitchTime {
   104  		flags = script.VER_P2SH
   105  	}
   106  
   107  	if block_height >= ch.Consensus.BIP66Height {
   108  		flags |= script.VER_DERSIG
   109  	}
   110  
   111  	if block_height >= ch.Consensus.BIP65Height {
   112  		flags |= script.VER_CLTV
   113  	}
   114  
   115  	if ch.Consensus.Enforce_CSV != 0 && block_height >= ch.Consensus.Enforce_CSV {
   116  		flags |= script.VER_CSV
   117  	}
   118  
   119  	if ch.Consensus.Enforce_SEGWIT != 0 && block_height >= ch.Consensus.Enforce_SEGWIT {
   120  		flags |= script.VER_WITNESS | script.VER_NULLDUMMY
   121  	}
   122  
   123  	if ch.Consensus.Enforce_Taproot != 0 && block_height >= ch.Consensus.Enforce_Taproot {
   124  		flags |= script.VER_TAPROOT
   125  	}
   126  
   127  	return
   128  }
   129  
   130  func (ch *Chain) ApplyBlockFlags(bl *btc.Block) {
   131  	bl.VerifyFlags = ch.GetBlockFlags(bl.Height, bl.BlockTime())
   132  }
   133  
   134  func (ch *Chain) PostCheckBlock(bl *btc.Block) (er error) {
   135  	// Size limits
   136  	if len(bl.Raw) < 81 {
   137  		er = errors.New("CheckBlock() : size limits failed low - RPC_Result:bad-blk-length")
   138  		return
   139  	}
   140  
   141  	if bl.Txs == nil {
   142  		er = bl.BuildTxList()
   143  		if er != nil {
   144  			return
   145  		}
   146  		if bl.BlockWeight > btc.MAX_BLOCK_WEIGHT {
   147  			er = errors.New("CheckBlock() : weight limits failed - RPC_Result:bad-blk-weight")
   148  			return
   149  		}
   150  		//fmt.Println("New block", bl.Height, " Weight:", bl.BlockWeight, " Raw:", len(bl.Raw))
   151  	}
   152  
   153  	if !bl.Trusted.Get() {
   154  		// We need to be satoshi compatible
   155  		if len(bl.Txs) == 0 || bl.Txs[0] == nil || !bl.Txs[0].IsCoinBase() {
   156  			er = errors.New("CheckBlock() : first tx is not coinbase: " + bl.Hash.String() + " - RPC_Result:bad-cb-missing")
   157  			return
   158  		}
   159  
   160  		// Enforce rule that the coinbase starts with serialized block height
   161  		if bl.Height >= ch.Consensus.BIP34Height {
   162  			var exp [6]byte
   163  			var exp_len int
   164  			binary.LittleEndian.PutUint32(exp[1:5], bl.Height)
   165  			for exp_len = 5; exp_len > 1; exp_len-- {
   166  				if exp[exp_len] != 0 || exp[exp_len-1] >= 0x80 {
   167  					break
   168  				}
   169  			}
   170  			exp[0] = byte(exp_len)
   171  			exp_len++
   172  
   173  			if !bytes.HasPrefix(bl.Txs[0].TxIn[0].ScriptSig, exp[:exp_len]) {
   174  				er = errors.New("CheckBlock() : Unexpected block number in coinbase: " + bl.Hash.String() + " - RPC_Result:bad-cb-height")
   175  				return
   176  			}
   177  		}
   178  
   179  		// And again...
   180  		for i := 1; i < len(bl.Txs); i++ {
   181  			if bl.Txs[i].IsCoinBase() {
   182  				er = errors.New("CheckBlock() : more than one coinbase: " + bl.Hash.String() + " - RPC_Result:bad-cb-multiple")
   183  				return
   184  			}
   185  		}
   186  	}
   187  
   188  	// Check Merkle Root, even for trusted blocks - that's important, as they may come from untrusted peers
   189  	merkle, mutated := bl.GetMerkle()
   190  	if mutated {
   191  		er = errors.New("CheckBlock(): duplicate transaction - RPC_Result:bad-txns-duplicate")
   192  		return
   193  	}
   194  
   195  	if !bytes.Equal(merkle, bl.MerkleRoot()) {
   196  		er = errors.New("CheckBlock() : Merkle Root mismatch - RPC_Result:bad-txnmrklroot")
   197  		return
   198  	}
   199  
   200  	ch.ApplyBlockFlags(bl)
   201  
   202  	if !bl.Trusted.Get() {
   203  		var blockTime uint32
   204  		var had_witness bool
   205  
   206  		if (bl.VerifyFlags & script.VER_CSV) != 0 {
   207  			blockTime = bl.MedianPastTime
   208  		} else {
   209  			blockTime = bl.BlockTime()
   210  		}
   211  
   212  		// Verify merkle root of witness data
   213  		if (bl.VerifyFlags & script.VER_WITNESS) != 0 {
   214  			var i int
   215  			for i = len(bl.Txs[0].TxOut) - 1; i >= 0; i-- {
   216  				o := bl.Txs[0].TxOut[i]
   217  				if len(o.Pk_script) >= 38 && bytes.Equal(o.Pk_script[:6], []byte{0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed}) {
   218  					if len(bl.Txs[0].SegWit) != 1 || len(bl.Txs[0].SegWit[0]) != 1 || len(bl.Txs[0].SegWit[0][0]) != 32 {
   219  						er = errors.New("CheckBlock() : invalid witness nonce size - RPC_Result:bad-witness-nonce-size")
   220  						println(er.Error())
   221  						println(bl.Hash.String(), len(bl.Txs[0].SegWit))
   222  						return
   223  					}
   224  
   225  					// The malleation check is ignored; as the transaction tree itself
   226  					// already does not permit it, it is impossible to trigger in the
   227  					// witness tree.
   228  					merkle, _ := btc.GetWitnessMerkle(bl.Txs)
   229  					with_nonce := btc.Sha2Sum(append(merkle, bl.Txs[0].SegWit[0][0]...))
   230  
   231  					if !bytes.Equal(with_nonce[:], o.Pk_script[6:38]) {
   232  						er = errors.New("CheckBlock(): Witness Merkle mismatch - RPC_Result:bad-witness-merkle-match")
   233  						return
   234  					}
   235  
   236  					had_witness = true
   237  					break
   238  				}
   239  			}
   240  		}
   241  
   242  		if !had_witness {
   243  			for _, t := range bl.Txs {
   244  				if t.SegWit != nil {
   245  					er = errors.New("CheckBlock(): unexpected witness data found - RPC_Result:unexpected-witness")
   246  					return
   247  				}
   248  			}
   249  		}
   250  
   251  		// Check transactions - this is the most time consuming task
   252  		er = CheckTransactions(bl.Txs, bl.Height, blockTime)
   253  	}
   254  	return
   255  }
   256  
   257  func (ch *Chain) CheckBlock(bl *btc.Block) (dos bool, maybelater bool, er error) {
   258  	dos, maybelater, er = ch.PreCheckBlock(bl)
   259  	if er == nil {
   260  		er = ch.PostCheckBlock(bl)
   261  		if er != nil { // all post-check errors are DoS kind
   262  			dos = true
   263  		}
   264  	}
   265  	return
   266  }