func (b *BlockDAG) checkBlockContext(block *soterutil.Block, prevNodes []*blockNode, flags BehaviorFlags) error {
	header := &block.MsgBlock().Header
	err := b.checkBlockHeaderContext(header, prevNodes, flags)
	if err != nil {
		return err
	}
	fastAdd := flags&BFFastAdd == BFFastAdd
	if !fastAdd {
	}
	return nil
}
checkBlockContext peforms several validation checks on the block which depend
on its position within the block chain.

The flags modify the behavior of this function as follows:
- BFFastAdd: The transaction are not checked to see if they are finalized
and the somewhat expensive BIP0034 validation is not performed.

The flags are also passed to checkBlockHeaderContext.  See its documentation
for how the flags modify its behavior.

This function MUST be called with the chain state lock held (for writes).
package blockdag
import (
	"encoding/binary"
	"fmt"
	"math"
	"math/big"
	"time"
	"github.com/soteria-dag/soterd/chaincfg"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/mining/cuckoo"
	"github.com/soteria-dag/soterd/soterutil"
	"github.com/soteria-dag/soterd/txscript"
	"github.com/soteria-dag/soterd/wire"
)
const (
	MaxTimeOffsetSeconds = 2 * 60 * 60
	MinCoinbaseScriptLen = 2
	MaxCoinbaseScriptLen = 100
	medianTimeBlocks = 11
	serializedHeightVersion = 2
	baseSubsidy = 50 * soterutil.NanoSoterPerSoter
)
var (
	zeroHash chainhash.Hash
)
func isNullOutpoint(outpoint *wire.OutPoint) bool {
	if outpoint.Index == math.MaxUint32 && outpoint.Hash == zeroHash {
		return true
	}
	return false
}
func ShouldHaveSerializedBlockHeight(header *wire.BlockHeader) bool {
	return header.Version >= serializedHeightVersion
}
func IsCoinBaseTx(msgTx *wire.MsgTx) bool {
	if len(msgTx.TxIn) != 1 {
		return false
	}
	prevOut := &msgTx.TxIn[0].PreviousOutPoint
	if prevOut.Index != math.MaxUint32 || prevOut.Hash != zeroHash {
		return false
	}
	return true
}
func IsCoinBase(tx *soterutil.Tx) bool {
	return IsCoinBaseTx(tx.MsgTx())
}
func SequenceLockActive(sequenceLock *SequenceLock, blockHeight int32,
	medianTimePast time.Time) bool {
	if sequenceLock.Seconds >= medianTimePast.Unix() ||
		sequenceLock.BlockHeight >= blockHeight {
		return false
	}
	return true
}
func IsFinalizedTransaction(tx *soterutil.Tx, blockHeight int32, blockTime time.Time) bool {
		msgTx := tx.MsgTx()
		lockTime := msgTx.LockTime
		if lockTime == 0 {
			return true
		}
		blockTimeOrHeight := int64(0)
		if lockTime < txscript.LockTimeThreshold {
			blockTimeOrHeight = int64(blockHeight)
		} else {
			blockTimeOrHeight = blockTime.Unix()
		}
		if int64(lockTime) < blockTimeOrHeight {
			return true
		}
		for _, txIn := range msgTx.TxIn {
			if txIn.Sequence != math.MaxUint32 {
				return false
			}
		}
		return true
}
func CalcBlockSubsidy(height int32, chainParams *chaincfg.Params) int64 {
	if chainParams.SubsidyReductionInterval == 0 {
		return baseSubsidy
	}
	return baseSubsidy >> uint(height/chainParams.SubsidyReductionInterval)
}
func CheckTransactionSanity(tx *soterutil.Tx) error {
	msgTx := tx.MsgTx()
	if len(msgTx.TxIn) == 0 {
		return ruleError(ErrNoTxInputs, "transaction has no inputs")
	}
	if len(msgTx.TxOut) == 0 {
		return ruleError(ErrNoTxOutputs, "transaction has no outputs")
	}
	serializedTxSize := tx.MsgTx().SerializeSizeStripped()
	if serializedTxSize > MaxBlockBaseSize {
		str := fmt.Sprintf("serialized transaction is too big - got "+
			"%d, max %d", serializedTxSize, MaxBlockBaseSize)
		return ruleError(ErrTxTooBig, str)
	}
	var totalNanoSoter int64
	for _, txOut := range msgTx.TxOut {
		nanoSoter := txOut.Value
		if nanoSoter < 0 {
			str := fmt.Sprintf("transaction output has negative "+
				"value of %v", nanoSoter)
			return ruleError(ErrBadTxOutValue, str)
		}
		if nanoSoter > soterutil.MaxNanoSoter {
			str := fmt.Sprintf("transaction output value of %v is "+
				"higher than max allowed value of %v", nanoSoter,
				soterutil.MaxNanoSoter)
			return ruleError(ErrBadTxOutValue, str)
		}
		totalNanoSoter += nanoSoter
		if totalNanoSoter < 0 {
			str := fmt.Sprintf("total value of all transaction "+
				"outputs exceeds max allowed value of %v",
				soterutil.MaxNanoSoter)
			return ruleError(ErrBadTxOutValue, str)
		}
		if totalNanoSoter > soterutil.MaxNanoSoter {
			str := fmt.Sprintf("total value of all transaction "+
				"outputs is %v which is higher than max "+
				"allowed value of %v", totalNanoSoter,
				soterutil.MaxNanoSoter)
			return ruleError(ErrBadTxOutValue, str)
		}
	}
	existingTxOut := make(map[wire.OutPoint]struct{})
	for _, txIn := range msgTx.TxIn {
		if _, exists := existingTxOut[txIn.PreviousOutPoint]; exists {
			return ruleError(ErrDuplicateTxInputs, "transaction "+
				"contains duplicate inputs")
		}
		existingTxOut[txIn.PreviousOutPoint] = struct{}{}
	}
	if IsCoinBase(tx) {
		slen := len(msgTx.TxIn[0].SignatureScript)
		if slen < MinCoinbaseScriptLen || slen > MaxCoinbaseScriptLen {
			str := fmt.Sprintf("coinbase transaction script length "+
				"of %d is out of range (min: %d, max: %d)",
				slen, MinCoinbaseScriptLen, MaxCoinbaseScriptLen)
			return ruleError(ErrBadCoinbaseScriptLen, str)
		}
	} else {
		for _, txIn := range msgTx.TxIn {
			if isNullOutpoint(&txIn.PreviousOutPoint) {
				return ruleError(ErrBadTxInput, "transaction "+
					"input refers to previous output that "+
					"is null")
			}
		}
	}
	return nil
}
func checkProofOfWork(solver cuckoo.Solver, block *wire.MsgBlock, powLimit *big.Int, flags BehaviorFlags) error {
	var header = block.Header
	target := CompactToBig(header.Bits)
	if target.Sign() <= 0 {
		str := fmt.Sprintf("block target difficulty of %064x is too low",
			target)
		return ruleError(ErrUnexpectedDifficulty, str)
	}
	if target.Cmp(powLimit) > 0 {
		str := fmt.Sprintf("block target difficulty of %064x is "+
			"higher than max of %064x", target, powLimit)
		return ruleError(ErrUnexpectedDifficulty, str)
	}
	if flags&BFNoPoWCheck != BFNoPoWCheck {
		headerBytes, err := header.Bytes()
		if err != nil {
			str := fmt.Sprintf("cuckoo cycle verification failed: %s", err)
			return ruleError(ErrCuckooFail, str)
		}
		err = solver.Verify(headerBytes, block.Header.Nonce, block.Verification.CycleNonces)
		if err != nil {
			str := fmt.Sprintf("cuckoo cycle verification failed: %s", err)
			return ruleError(ErrCuckooFail, str)
		}
	}
	return nil
}
func CheckProofOfWork(solver cuckoo.Solver, block *soterutil.Block, powLimit *big.Int) error {
	return checkProofOfWork(solver, block.MsgBlock(), powLimit, BFNone)
}
func CountSigOps(tx *soterutil.Tx) int {
	msgTx := tx.MsgTx()
	totalSigOps := 0
	for _, txIn := range msgTx.TxIn {
		numSigOps := txscript.GetSigOpCount(txIn.SignatureScript)
		totalSigOps += numSigOps
	}
	for _, txOut := range msgTx.TxOut {
		numSigOps := txscript.GetSigOpCount(txOut.PkScript)
		totalSigOps += numSigOps
	}
	return totalSigOps
}
func CountP2SHSigOps(tx *soterutil.Tx, isCoinBaseTx bool, utxoView *UtxoViewpoint) (int, error) {
	if isCoinBaseTx {
		return 0, nil
	}
	msgTx := tx.MsgTx()
	totalSigOps := 0
	for txInIndex, txIn := range msgTx.TxIn {
		utxo := utxoView.LookupEntry(txIn.PreviousOutPoint)
		if utxo == nil || utxo.IsSpent() {
			str := fmt.Sprintf("output %v referenced from "+
				"transaction %s:%d either does not exist or "+
				"has already been spent", txIn.PreviousOutPoint,
				tx.Hash(), txInIndex)
			return 0, ruleError(ErrMissingTxOut, str)
		}
		pkScript := utxo.PkScript()
		if !txscript.IsPayToScriptHash(pkScript) {
			continue
		}
		sigScript := txIn.SignatureScript
		numSigOps := txscript.GetPreciseSigOpCount(sigScript, pkScript,
			true)
		lastSigOps := totalSigOps
		totalSigOps += numSigOps
		if totalSigOps < lastSigOps {
			str := fmt.Sprintf("the public key script from output "+
				"%v contains too many signature operations - "+
				"overflow", txIn.PreviousOutPoint)
			return 0, ruleError(ErrTooManySigOps, str)
		}
	}
	return totalSigOps, nil
}
func checkBlockHeaderSanity(solver cuckoo.Solver, block *wire.MsgBlock, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error {
	var header = block.Header
	err := checkProofOfWork(solver, block, powLimit, flags)
	if err != nil {
		return err
	}
	if !header.Timestamp.Equal(time.Unix(header.Timestamp.Unix(), 0)) {
		str := fmt.Sprintf("block timestamp of %v has a higher "+
			"precision than one second", header.Timestamp)
		return ruleError(ErrInvalidTime, str)
	}
	maxTimestamp := timeSource.AdjustedTime().Add(time.Second *
		MaxTimeOffsetSeconds)
	if header.Timestamp.After(maxTimestamp) {
		str := fmt.Sprintf("block timestamp of %v is too far in the "+
			"future", header.Timestamp)
		return ruleError(ErrTimeTooNew, str)
	}
	return nil
}
func checkBlockSanity(solver cuckoo.Solver, block *soterutil.Block, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error {
	msgBlock := block.MsgBlock()
	header := &msgBlock.Header
	err := checkBlockHeaderSanity(solver, block.MsgBlock(), powLimit, timeSource, flags)
	if err != nil {
		return err
	}
	numTx := len(msgBlock.Transactions)
	if numTx == 0 {
		return ruleError(ErrNoTransactions, "block does not contain "+
			"any transactions")
	}
	if numTx > MaxBlockBaseSize {
		str := fmt.Sprintf("block contains too many transactions - "+
			"got %d, max %d", numTx, MaxBlockBaseSize)
		return ruleError(ErrBlockTooBig, str)
	}
	serializedSize := msgBlock.SerializeSizeStripped()
	if serializedSize > MaxBlockBaseSize {
		str := fmt.Sprintf("serialized block is too big - got %d, "+
			"max %d", serializedSize, MaxBlockBaseSize)
		return ruleError(ErrBlockTooBig, str)
	}
	transactions := block.Transactions()
	if !IsCoinBase(transactions[0]) {
		return ruleError(ErrFirstTxNotCoinbase, "first transaction in "+
			"block is not a coinbase")
	}
	for i, tx := range transactions[1:] {
		if IsCoinBase(tx) {
			str := fmt.Sprintf("block contains second coinbase at "+
				"index %d", i+1)
			return ruleError(ErrMultipleCoinbases, str)
		}
	}
	for _, tx := range transactions {
		err := CheckTransactionSanity(tx)
		if err != nil {
			return err
		}
	}
	merkles := BuildMerkleTreeStore(block.Transactions(), false)
	calculatedMerkleRoot := merkles[len(merkles)-1]
	if !header.MerkleRoot.IsEqual(calculatedMerkleRoot) {
		str := fmt.Sprintf("block merkle root is invalid - block "+
			"header indicates %v, but calculated value is %v",
			header.MerkleRoot, calculatedMerkleRoot)
		return ruleError(ErrBadMerkleRoot, str)
	}
	existingTxHashes := make(map[chainhash.Hash]struct{})
	for _, tx := range transactions {
		hash := tx.Hash()
		if _, exists := existingTxHashes[*hash]; exists {
			str := fmt.Sprintf("block contains duplicate "+
				"transaction %v", hash)
			return ruleError(ErrDuplicateTx, str)
		}
		existingTxHashes[*hash] = struct{}{}
	}
	totalSigOps := 0
	for _, tx := range transactions {
		lastSigOps := totalSigOps
		totalSigOps += (CountSigOps(tx) * WitnessScaleFactor)
		if totalSigOps < lastSigOps || totalSigOps > MaxBlockSigOpsCost {
			str := fmt.Sprintf("block contains too many signature "+
				"operations - got %v, max %v", totalSigOps,
				MaxBlockSigOpsCost)
			return ruleError(ErrTooManySigOps, str)
		}
	}
	return nil
}
func CheckBlockSanity(solver cuckoo.Solver, block *soterutil.Block, powLimit *big.Int, timeSource MedianTimeSource) error {
	return checkBlockSanity(solver, block, powLimit, timeSource, BFNone)
}
func ExtractCoinbaseHeight(coinbaseTx *soterutil.Tx) (int32, error) {
	sigScript := coinbaseTx.MsgTx().TxIn[0].SignatureScript
	if len(sigScript) < 1 {
		str := "the coinbase signature script for blocks of " +
			"version %d or greater must start with the " +
			"length of the serialized block height"
		str = fmt.Sprintf(str, serializedHeightVersion)
		return 0, ruleError(ErrMissingCoinbaseHeight, str)
	}
	opcode := int(sigScript[0])
	if opcode == txscript.OP_0 {
		return 0, nil
	}
	if opcode >= txscript.OP_1 && opcode <= txscript.OP_16 {
		return int32(opcode - (txscript.OP_1 - 1)), nil
	}
	serializedLen := int(sigScript[0])
	if len(sigScript[1:]) < serializedLen {
		str := "the coinbase signature script for blocks of " +
			"version %d or greater must start with the " +
			"serialized block height"
		str = fmt.Sprintf(str, serializedLen)
		return 0, ruleError(ErrMissingCoinbaseHeight, str)
	}
	serializedHeightBytes := make([]byte, 8)
	copy(serializedHeightBytes, sigScript[1:serializedLen+1])
	serializedHeight := binary.LittleEndian.Uint64(serializedHeightBytes)
	return int32(serializedHeight), nil
}
func checkSerializedHeight(coinbaseTx *soterutil.Tx, wantHeight int32) error {
	serializedHeight, err := ExtractCoinbaseHeight(coinbaseTx)
	if err != nil {
		return err
	}
	if serializedHeight != wantHeight {
		str := fmt.Sprintf("the coinbase signature script serialized "+
			"block height is %d when %d was expected",
			serializedHeight, wantHeight)
		return ruleError(ErrBadCoinbaseHeight, str)
	}
	return nil
}
func (b *BlockDAG) checkBlockHeaderContext(header *wire.BlockHeader, prevNodes []*blockNode, flags BehaviorFlags) error {
	fastAdd := flags&BFFastAdd == BFFastAdd
	if !fastAdd {
		var recentTip *blockNode
		var recentTipTime = time.Time{}.Unix()  
		var maxHeight int32
		for _, prevNode := range prevNodes {
			if prevNode.timestamp > recentTipTime {
				recentTip = prevNode
				recentTipTime = prevNode.timestamp
			}
			if prevNode.height > maxHeight {
				maxHeight = prevNode.height
			}
		}
		target, err := b.TargetDifficulty(maxHeight)
		if err != nil {
			return err
		}
		expectedDifficulty := BigToCompact(target)
		blockDifficulty := header.Bits
		if blockDifficulty != expectedDifficulty {
			str := "block difficulty of %d is not the expected value of %d"
			str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
			return ruleError(ErrUnexpectedDifficulty, str)
		}
		medianTime := recentTip.CalcPastMedianTime()
		if !header.Timestamp.After(medianTime) {
			str := "block timestamp of %v is not after expected %v"
			str = fmt.Sprintf(str, header.Timestamp, medianTime)
			return ruleError(ErrTimeTooOld, str)
		}
	}
	return nil
}