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

     1  package chain
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"sync/atomic"
     8  
     9  	"github.com/piotrnar/gocoin/lib/btc"
    10  	"github.com/piotrnar/gocoin/lib/script"
    11  	"github.com/piotrnar/gocoin/lib/utxo"
    12  )
    13  
    14  // TrustedTxChecker is meant to speed up verifying transactions that had
    15  // been verified already by the client while being taken to its memory pool
    16  var TrustedTxChecker func(*btc.Tx) bool
    17  
    18  func (ch *Chain) ProcessBlockTransactions(bl *btc.Block, height, lknown uint32) (changes *utxo.BlockChanges, sigopscost uint32, e error) {
    19  	changes = new(utxo.BlockChanges)
    20  	changes.Height = height
    21  	changes.LastKnownHeight = lknown
    22  	changes.DeledTxs = make(map[[32]byte][]bool, bl.TotalInputs)
    23  	sigopscost, e = ch.commitTxs(bl, changes)
    24  	return
    25  }
    26  
    27  // AcceptBlock either appends a new block at the end of the existing chain
    28  // in which case it also applies all the transactions to the unspent database.
    29  // If the block does is not the heighest, it is added to the chain, but maked
    30  // as an orphan - its transaction will be verified only if the chain would swap
    31  // to its branch later on.
    32  func (ch *Chain) AcceptBlock(bl *btc.Block) (e error) {
    33  	ch.BlockIndexAccess.Lock()
    34  	cur := ch.AcceptHeader(bl)
    35  	ch.BlockIndexAccess.Unlock()
    36  	return ch.CommitBlock(bl, cur)
    37  }
    38  
    39  // Make sure to call this function with ch.BlockIndexAccess locked
    40  func (ch *Chain) AcceptHeader(bl *btc.Block) (cur *BlockTreeNode) {
    41  	prevblk, ok := ch.BlockIndex[btc.NewUint256(bl.ParentHash()).BIdx()]
    42  	if !ok {
    43  		panic("This should not happen")
    44  	}
    45  
    46  	// create new BlockTreeNode
    47  	cur = new(BlockTreeNode)
    48  	cur.BlockHash = bl.Hash
    49  	cur.Parent = prevblk
    50  	cur.Height = prevblk.Height + 1
    51  	copy(cur.BlockHeader[:], bl.Raw[:80])
    52  
    53  	// Add this block to the block index
    54  	prevblk.addChild(cur)
    55  	ch.BlockIndex[cur.BlockHash.BIdx()] = cur
    56  
    57  	return
    58  }
    59  
    60  func (ch *Chain) CommitBlock(bl *btc.Block, cur *BlockTreeNode) (e error) {
    61  	cur.BlockSize = uint32(len(bl.Raw))
    62  	cur.TxCount = uint32(bl.TxCount)
    63  	if ch.LastBlock() == cur.Parent {
    64  		// The head of out chain - apply the transactions
    65  		var changes *utxo.BlockChanges
    66  		var sigopscost uint32
    67  		changes, sigopscost, e = ch.ProcessBlockTransactions(bl, cur.Height, bl.LastKnownHeight)
    68  		if e != nil {
    69  			// ProcessBlockTransactions failed, so trash the block.
    70  			//println("ProcessBlockTransactionsA", cur.BlockHash.String(), cur.Height, e.Error())
    71  			ch.BlockIndexAccess.Lock()
    72  			cur.Parent.delChild(cur)
    73  			delete(ch.BlockIndex, cur.BlockHash.BIdx())
    74  			ch.BlockIndexAccess.Unlock()
    75  		} else {
    76  			cur.SigopsCost = sigopscost
    77  			// ProcessBlockTransactions succeeded, so save the block as "trusted".
    78  			bl.Trusted.Set()
    79  			ch.Blocks.BlockAdd(cur.Height, bl)
    80  			// Apply the block's trabnsactions to the unspent database:
    81  			ch.Unspent.CommitBlockTxs(changes, bl.Hash.Hash[:])
    82  			ch.SetLast(cur) // Advance the head
    83  			if ch.CB.BlockMinedCB != nil {
    84  				ch.CB.BlockMinedCB(bl)
    85  			}
    86  		}
    87  	} else {
    88  		// The block's parent is not the current head of the chain...
    89  
    90  		// Save the block, though do not mark it as "trusted" just yet
    91  		ch.Blocks.BlockAdd(cur.Height, bl)
    92  
    93  		// If it has more POW than the current head, move the head to it
    94  		if cur.MorePOW(ch.LastBlock()) {
    95  			ch.MoveToBlock(cur)
    96  			if ch.LastBlock() != cur {
    97  				e = errors.New("CommitBlock: MoveToBlock failed")
    98  			}
    99  		} else {
   100  			println("Orphaned block", bl.Hash.String(), cur.Height)
   101  		}
   102  	}
   103  
   104  	return
   105  }
   106  
   107  // commitTxs is ususually the most time consuming process when applying a new block.
   108  func (ch *Chain) commitTxs(bl *btc.Block, changes *utxo.BlockChanges) (sigopscost uint32, e error) {
   109  	sumblockin := btc.GetBlockReward(changes.Height)
   110  	var txoutsum, txinsum, sumblockout uint64
   111  
   112  	if changes.Height+ch.Unspent.UnwindBufLen >= changes.LastKnownHeight {
   113  		changes.UndoData = make(map[[32]byte]*utxo.UtxoRec)
   114  	}
   115  
   116  	blUnsp := make(map[[32]byte][]*btc.TxOut, len(bl.Txs))
   117  
   118  	var wg sync.WaitGroup
   119  	var ver_err_cnt uint32
   120  
   121  	for i, tx := range bl.Txs {
   122  		txoutsum, txinsum = 0, 0
   123  
   124  		sigopscost += uint32(btc.WITNESS_SCALE_FACTOR * bl.Txs[i].GetLegacySigOpCount())
   125  
   126  		// Check each tx for a valid input, except from the first one
   127  		if i > 0 {
   128  
   129  			tx_trusted := bl.Trusted.Get()
   130  			if !tx_trusted {
   131  				if TrustedTxChecker != nil && TrustedTxChecker(bl.Txs[i]) {
   132  					tx_trusted = true
   133  				} else {
   134  					tx.Spent_outputs = make([]*btc.TxOut, len(bl.Txs[i].TxIn))
   135  				}
   136  			}
   137  
   138  			// first collect all the inputs, their amounts and spend scripts
   139  			for j := 0; j < len(bl.Txs[i].TxIn); j++ {
   140  				inp := &bl.Txs[i].TxIn[j].Input
   141  				spent_map, was_spent := changes.DeledTxs[inp.Hash]
   142  				if was_spent {
   143  					if int(inp.Vout) >= len(spent_map) {
   144  						println("txin", inp.String(), "did not have vout", inp.Vout)
   145  						e = errors.New("tx VOut too big")
   146  						return
   147  					}
   148  
   149  					if spent_map[inp.Vout] {
   150  						println("txin", inp.String(), "already spent in this block")
   151  						e = errors.New("double spend inside the block")
   152  						return
   153  					}
   154  				}
   155  				tout := ch.Unspent.UnspentGet(inp)
   156  				if tout == nil {
   157  					t, ok := blUnsp[inp.Hash]
   158  					if !ok {
   159  						e = errors.New("Unknown input TxID: " + btc.NewUint256(inp.Hash[:]).String())
   160  						return
   161  					}
   162  
   163  					if inp.Vout >= uint32(len(t)) {
   164  						println("Vout too big", len(t), inp.String())
   165  						e = errors.New("vout too big")
   166  						return
   167  					}
   168  
   169  					if t[inp.Vout] == nil {
   170  						println("Vout already spent", inp.String())
   171  						e = errors.New("vout already spent")
   172  						return
   173  					}
   174  
   175  					if t[inp.Vout].WasCoinbase {
   176  						e = errors.New("Cannot spend block's own coinbase in TxID: " + btc.NewUint256(inp.Hash[:]).String())
   177  						return
   178  					}
   179  
   180  					tout = t[inp.Vout]
   181  					t[inp.Vout] = nil // and now mark it as spent:
   182  				} else {
   183  					if tout.WasCoinbase && changes.Height-tout.BlockHeight < COINBASE_MATURITY {
   184  						e = errors.New("Trying to spend prematured coinbase: " + btc.NewUint256(inp.Hash[:]).String())
   185  						return
   186  					}
   187  					// it is confirmed already so delete it later
   188  					if !was_spent {
   189  						spent_map = make([]bool, tout.VoutCount)
   190  						changes.DeledTxs[inp.Hash] = spent_map
   191  					}
   192  					spent_map[inp.Vout] = true
   193  
   194  					if changes.UndoData != nil {
   195  						var urec *utxo.UtxoRec
   196  						urec = changes.UndoData[inp.Hash]
   197  						if urec == nil {
   198  							urec = new(utxo.UtxoRec)
   199  							urec.TxID = inp.Hash
   200  							urec.Coinbase = tout.WasCoinbase
   201  							urec.InBlock = tout.BlockHeight
   202  							urec.Outs = make([]*utxo.UtxoTxOut, tout.VoutCount)
   203  							changes.UndoData[inp.Hash] = urec
   204  						}
   205  						tmp := new(utxo.UtxoTxOut)
   206  						tmp.Value = tout.Value
   207  						tmp.PKScr = make([]byte, len(tout.Pk_script))
   208  						copy(tmp.PKScr, tout.Pk_script)
   209  						urec.Outs[inp.Vout] = tmp
   210  					}
   211  				}
   212  
   213  				if !tx_trusted {
   214  					tx.Spent_outputs[j] = tout
   215  				}
   216  
   217  				if (bl.VerifyFlags & script.VER_P2SH) != 0 {
   218  					if btc.IsP2SH(tout.Pk_script) {
   219  						sigopscost += uint32(btc.WITNESS_SCALE_FACTOR * btc.GetP2SHSigOpCount(bl.Txs[i].TxIn[j].ScriptSig))
   220  					}
   221  				}
   222  
   223  				if (bl.VerifyFlags & script.VER_WITNESS) != 0 {
   224  					sigopscost += uint32(bl.Txs[i].CountWitnessSigOps(j, tout.Pk_script))
   225  				}
   226  
   227  				txinsum += tout.Value
   228  			}
   229  
   230  			// second, verify the scrips:
   231  			if !tx_trusted { // run VerifyTxScript() in a parallel task
   232  				for j := 0; j < len(bl.Txs[i].TxIn); j++ {
   233  					wg.Add(1)
   234  					go func(i int, tx *btc.Tx) {
   235  						if !script.VerifyTxScript(tx.Spent_outputs[i].Pk_script, &script.SigChecker{Amount: tx.Spent_outputs[i].Value, Idx: i, Tx: tx}, bl.VerifyFlags) {
   236  							atomic.AddUint32(&ver_err_cnt, 1)
   237  						}
   238  						wg.Done()
   239  					}(j, bl.Txs[i])
   240  				}
   241  			}
   242  
   243  		} else {
   244  			// For coinbase tx we need to check (like satoshi) whether the script size is between 2 and 100 bytes
   245  			// (Previously we made sure in CheckBlock() that this was a coinbase type tx)
   246  			if len(bl.Txs[0].TxIn[0].ScriptSig) < 2 || len(bl.Txs[0].TxIn[0].ScriptSig) > 100 {
   247  				e = errors.New(fmt.Sprint("Coinbase script has a wrong length ", len(bl.Txs[0].TxIn[0].ScriptSig)))
   248  				return
   249  			}
   250  		}
   251  
   252  		if e != nil { // this should not happen as every error has a return
   253  			return // If any input fails, do not continue
   254  		}
   255  
   256  		sumblockin += txinsum
   257  
   258  		for j := range bl.Txs[i].TxOut {
   259  			txoutsum += bl.Txs[i].TxOut[j].Value
   260  		}
   261  		sumblockout += txoutsum
   262  
   263  		if i > 0 {
   264  			bl.Txs[i].Fee = txinsum - txoutsum
   265  			if txoutsum > txinsum {
   266  				e = fmt.Errorf("more spent (%.8f) than at the input (%.8f) in TX %s",
   267  					float64(txoutsum)/1e8, float64(txinsum)/1e8, bl.Txs[i].Hash.String())
   268  				return
   269  			}
   270  		}
   271  
   272  		// Add each tx outs from the currently executed TX to the temporary pool
   273  		outs := make([]*btc.TxOut, len(bl.Txs[i].TxOut))
   274  		copy(outs, bl.Txs[i].TxOut)
   275  		blUnsp[bl.Txs[i].Hash.Hash] = outs
   276  	}
   277  
   278  	if !bl.Trusted.Get() {
   279  		wg.Wait()
   280  		if ver_err_cnt > 0 {
   281  			println("VerifyScript failed", ver_err_cnt, "time (s)")
   282  			e = errors.New(fmt.Sprint("VerifyScripts failed ", ver_err_cnt, "time (s)"))
   283  			return
   284  		}
   285  	}
   286  
   287  	if sumblockin < sumblockout {
   288  		e = fmt.Errorf("out:%d > in:%d", sumblockout, sumblockin)
   289  		return
   290  	}
   291  
   292  	if sigopscost > btc.MAX_BLOCK_SIGOPS_COST {
   293  		e = errors.New("commitTxs(): too many sigops - RPC_Result:bad-blk-sigops")
   294  		return
   295  	}
   296  
   297  	var rec *utxo.UtxoRec
   298  	changes.AddList = make([]*utxo.UtxoRec, 0, len(blUnsp))
   299  	for k, v := range blUnsp {
   300  		for i := range v {
   301  			if v[i] != nil {
   302  				if rec == nil {
   303  					rec = new(utxo.UtxoRec)
   304  					rec.TxID = k
   305  					rec.Coinbase = v[i].WasCoinbase
   306  					rec.InBlock = changes.Height
   307  					rec.Outs = make([]*utxo.UtxoTxOut, len(v))
   308  				}
   309  				rec.Outs[i] = &utxo.UtxoTxOut{Value: v[i].Value, PKScr: v[i].Pk_script}
   310  			}
   311  		}
   312  		if rec != nil {
   313  			changes.AddList = append(changes.AddList, rec)
   314  			rec = nil
   315  		}
   316  	}
   317  
   318  	return
   319  }
   320  
   321  // CheckTransactions checks transactions for consistency and finality.
   322  // Return nil if OK, otherwise a descripive error.
   323  func CheckTransactions(txs []*btc.Tx, height, btime uint32) (res error) {
   324  	var wg sync.WaitGroup
   325  
   326  	res_chan := make(chan error, 1)
   327  
   328  	for i := 0; len(res_chan) == 0 && i < len(txs); i++ {
   329  		wg.Add(1)
   330  
   331  		go func(tx *btc.Tx) {
   332  			defer wg.Done() // call wg.Done() before returning from this goroutine
   333  
   334  			if len(res_chan) > 0 {
   335  				return // abort checking if a parallel error has already been reported
   336  			}
   337  
   338  			er := tx.CheckTransaction()
   339  
   340  			if len(res_chan) > 0 {
   341  				return // abort checking if a parallel error has already been reported
   342  			}
   343  
   344  			if er == nil && !tx.IsFinal(height, btime) {
   345  				er = errors.New("CheckTransactions() : not-final transaction - RPC_Result:bad-txns-nonfinal")
   346  			}
   347  
   348  			if er != nil {
   349  				select { // this is a non-blocking write to channel
   350  				case res_chan <- er:
   351  				default:
   352  				}
   353  			}
   354  		}(txs[i])
   355  	}
   356  
   357  	wg.Wait() // wait for all the goroutines to complete
   358  
   359  	if len(res_chan) > 0 {
   360  		res = <-res_chan
   361  	}
   362  
   363  	return
   364  }