// ProcessTransaction is the main workhorse for handling insertion of new // free-standing transactions into a memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. func (mp *txMemPool) ProcessTransaction(tx *btcwire.MsgTx) error { txHash, err := tx.TxSha() if err != nil { return err } log.Tracef("[TXMP] Processing transaction %v", txHash) // Potentially accept the transaction to the memory pool. var isOrphan bool err = mp.maybeAcceptTransaction(tx, &isOrphan) if err != nil { return err } if !isOrphan { // Accept any orphan transactions that depend on this // transaction (they are no longer orphans) and repeat for those // accepted transactions until there are no more. err = mp.processOrphans(&txHash) if err != nil { return err } } else { // When the transaction is an orphan (has inputs missing), // potentially add it to the orphan pool. err := mp.maybeAddOrphan(tx, &txHash) if err != nil { return err } } return nil }
// removeTransaction removes the passed transaction from the memory pool. func (mp *txMemPool) removeTransaction(tx *btcwire.MsgTx) { mp.lock.Lock() defer mp.lock.Unlock() // Remove any transactions which rely on this one. txHash, _ := tx.TxSha() for i := uint32(0); i < uint32(len(tx.TxOut)); i++ { outpoint := btcwire.NewOutPoint(&txHash, i) if txRedeemer, exists := mp.outpoints[*outpoint]; exists { mp.lock.Unlock() mp.removeTransaction(txRedeemer) mp.lock.Lock() } } // Remove the transaction and mark the referenced outpoints as unspent // by the pool. if tx, exists := mp.pool[txHash]; exists { for _, txIn := range tx.TxIn { delete(mp.outpoints, txIn.PreviousOutpoint) } delete(mp.pool, txHash) } }
// handleTxMsg is invoked when a peer receives a tx bitcoin message. It blocks // until the bitcoin transaction has been fully processed. Unlock the block // handler this does not serialize all transactions through a single thread // transactions don't rely on the previous one in a linear fashion like blocks. func (p *peer) handleTxMsg(msg *btcwire.MsgTx) { // Add the transaction to the known inventory for the peer. hash, err := msg.TxSha() if err != nil { log.Errorf("Unable to get transaction hash: %v", err) return } iv := btcwire.NewInvVect(btcwire.InvVect_Tx, &hash) p.addKnownInventory(iv) // Process the transaction. err = p.server.txMemPool.ProcessTransaction(msg) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. if _, ok := err.(TxRuleError); ok { log.Infof("Rejected transaction %v: %v", hash, err) } else { log.Errorf("Failed to process transaction %v: %v", hash, err) } return } }
// Creates a new bulletin from the containing Tx, supplied author and optional blockhash // by unpacking txOuts that are considered data. It ignores extra junk behind the protobuffer. // NewBulletin also asserts aspects of valid bulletins by throwing errors when msg len // is zero or board len is greater than MaxBoardLen. func NewBulletin(tx *btcwire.MsgTx, blkhash *btcwire.ShaHash, net *btcnet.Params) (*Bulletin, error) { wireBltn := &wirebulletin.WireBulletin{} author, err := getAuthor(tx, net) if err != nil { return nil, err } // TODO. scrutinize. // Bootleg solution, but if unmarshal fails slice txout and try again until we can try no more or it fails for j := len(tx.TxOut); j > 1; j-- { rel_txouts := tx.TxOut[:j] // slice off change txouts bytes, err := extractData(rel_txouts) if err != nil { continue } err = proto.Unmarshal(bytes, wireBltn) if err != nil { continue } else { // No errors, we found a good decode break } } if err != nil { return nil, err } board := wireBltn.GetBoard() // assert that the length of the board is within its max size! if len(board) > MaxBoardLen { return nil, ErrMaxBoardLen } msg := wireBltn.GetMessage() // assert that the bulletin has a non zero message length. if len(msg) < 1 { return nil, ErrNoMsg } // TODO assert that msg and board are valid UTF-8 strings. hash, _ := tx.TxSha() bltn := &Bulletin{ Txid: &hash, Block: blkhash, Author: author, Board: board, Message: msg, Timestamp: time.Unix(wireBltn.GetTimestamp(), 0), } return bltn, nil }
// handleDecodeRawTransaction handles decoderawtransaction commands. func handleDecodeRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.DecodeRawTransactionCmd) // Deserialize the transaction. hexStr := c.HexTx if len(hexStr)%2 != 0 { hexStr = "0" + hexStr } serializedTx, err := hex.DecodeString(hexStr) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInvalidParameter.Code, Message: fmt.Sprintf("argument must be hexadecimal "+ "string (not %q)", hexStr), } } var mtx btcwire.MsgTx err = mtx.Deserialize(bytes.NewBuffer(serializedTx)) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "TX decode failed", } } txSha, _ := mtx.TxSha() vin, err := createVinList(&mtx) if err != nil { return nil, err } vout, err := createVoutList(&mtx, s.server.btcnet) if err != nil { return nil, err } // Create and return the result. txReply := btcjson.TxRawDecodeResult{ Txid: txSha.String(), Version: mtx.Version, Locktime: mtx.LockTime, Vin: vin, Vout: vout, } return txReply, nil }
// maybeAcceptTransaction is the main workhorse for handling insertion of new // free-standing transactions into a memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) error { *isOrphan = false txHash, err := tx.TxSha() if err != nil { return err } // Don't accept the transaction if it already exists in the pool. This // applies to orphan transactions as well. This check is intended to // be a quick check to weed out duplicates. It is more expensive to // detect a duplicate transaction in the main chain, so that is done // later. if mp.isTransactionInPool(&txHash) { str := fmt.Sprintf("already have transaction %v", txHash) return TxRuleError(str) } // Perform preliminary sanity checks on the transaction. This makes // use of btcchain which contains the invariant rules for what // transactions are allowed into blocks. err = btcchain.CheckTransactionSanity(tx) if err != nil { if _, ok := err.(btcchain.RuleError); ok { return TxRuleError(err.Error()) } return err } // A standalone transaction must not be a coinbase transaction. if btcchain.IsCoinBase(tx) { str := fmt.Sprintf("transaction %v is an individual coinbase", txHash) return TxRuleError(str) } // Don't accept transactions with a lock time after the maximum int32 // value for now. This is an artifact of older bitcoind clients which // treated this field as an int32 and would treat anything larger // incorrectly (as negative). if tx.LockTime > math.MaxInt32 { str := fmt.Sprintf("transaction %v is has a lock time after "+ "2038 which is not accepted yet", txHash) return TxRuleError(str) } // Get the current height of the main chain. A standalone transaction // will be mined into the next block at best, so _, curHeight, err := mp.server.db.NewestSha() if err != nil { return err } nextBlockHeight := curHeight + 1 // Don't allow non-standard transactions on the main network. if activeNetParams.btcnet == btcwire.MainNet { err := checkTransactionStandard(tx, nextBlockHeight) if err != nil { str := fmt.Sprintf("transaction %v is not a standard "+ "transaction: %v", txHash, err) return TxRuleError(str) } } // The transaction may not use any of the same outputs as other // transactions already in the pool as that would ultimately result in a // double spend. This check is intended to be quick and therefore only // detects double spends within the transaction pool itself. The // transaction could still be double spending coins from the main chain // at this point. There is a more in-depth check that happens later // after fetching the referenced transaction inputs from the main chain // which examines the actual spend data and prevents double spends. err = mp.checkPoolDoubleSpend(tx) if err != nil { return err } // Fetch all of the transactions referenced by the inputs to this // transaction. This function also attempts to fetch the transaction // itself to be used for detecting a duplicate transaction without // needing to do a separate lookup. txStore, err := mp.fetchInputTransactions(tx) if err != nil { return err } // Don't allow the transaction if it exists in the main chain and is not // not already fully spent. if txD, exists := txStore[txHash]; exists && txD.Err == nil { for _, isOutputSpent := range txD.Spent { if !isOutputSpent { str := fmt.Sprintf("transaction already exists") return TxRuleError(str) } } } delete(txStore, txHash) // Transaction is an orphan if any of the inputs don't exist. for _, txD := range txStore { if txD.Err == btcdb.TxShaMissing { *isOrphan = true return nil } } // Perform several checks on the transaction inputs using the invariant // rules in btcchain for what transactions are allowed into blocks. // Also returns the fees associated with the transaction which will be // used later. txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore) if err != nil { return err } // Don't allow transactions with non-standard inputs on the main // network. if activeNetParams.btcnet == btcwire.MainNet { err := checkInputsStandard(tx) if err != nil { str := fmt.Sprintf("transaction %v has a non-standard "+ "input: %v", txHash, err) return TxRuleError(str) } } // Note: if you modify this code to accept non-standard transactions, // you should add code here to check that the transaction does a // reasonable number of ECDSA signature verifications. // TODO(davec): Don't allow the transaction if the transation fee // would be too low to get into an empty block. _ = txFee // Verify crypto signatures for each input and reject the transaction if // any don't verify. err = btcchain.ValidateTransactionScripts(tx, &txHash, time.Now(), txStore) if err != nil { return err } // TODO(davec): Rate-limit free transactions // Add to transaction pool. mp.addTransaction(tx, &txHash) mp.lock.RLock() log.Debugf("[TXMP] Accepted transaction %v (pool size: %v)", txHash, len(mp.pool)) mp.lock.RUnlock() // TODO(davec): Notifications // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvVect_Tx, &txHash) mp.server.RelayInventory(iv) return nil }