// AddUnconfirmedTx adds all addresses related to the transaction to the // unconfirmed (memory-only) address index. // // NOTE: This transaction MUST have already been validated by the memory pool // before calling this function with it and have all of the inputs available in // the provided utxo view. Failure to do so could result in some or all // addresses not being indexed. // // This function is safe for concurrent access. func (idx *AddrIndex) AddUnconfirmedTx(tx *dcrutil.Tx, utxoView *blockchain.UtxoViewpoint) { // Index addresses of all referenced previous transaction outputs. // // The existence checks are elided since this is only called after the // transaction has already been validated and thus all inputs are // already known to exist. msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) for i, txIn := range msgTx.TxIn { // Skip stakebase. if i == 0 && isSSGen { continue } entry := utxoView.LookupEntry(&txIn.PreviousOutPoint.Hash) if entry == nil { // Ignore missing entries. This should never happen // in practice since the function comments specifically // call out all inputs must be available. continue } version := entry.ScriptVersionByIndex(txIn.PreviousOutPoint.Index) pkScript := entry.PkScriptByIndex(txIn.PreviousOutPoint.Index) txType := entry.TransactionType() idx.indexUnconfirmedAddresses(version, pkScript, tx, txType == stake.TxTypeSStx) } // Index addresses of all created outputs. isSStx, _ := stake.IsSStx(msgTx) for _, txOut := range msgTx.TxOut { idx.indexUnconfirmedAddresses(txOut.Version, txOut.PkScript, tx, isSStx) } }
// FetchTransactionStore fetches the input transactions referenced by the // passed transaction from the point of view of the end of the main chain. It // also attempts to fetch the transaction itself so the returned TxStore can be // examined for duplicate transactions. // IsValid indicates if the current block on head has had its TxTreeRegular // validated by the stake voters. func (b *BlockChain) FetchTransactionStore(tx *dcrutil.Tx, isValid bool) (TxStore, error) { isSSGen, _ := stake.IsSSGen(tx) // Create a set of needed transactions from the transactions referenced // by the inputs of the passed transaction. Also, add the passed // transaction itself as a way for the caller to detect duplicates. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} for i, txIn := range tx.MsgTx().TxIn { // Skip all stakebase inputs. if isSSGen && (i == 0) { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } // Request the input transactions from the point of view of the end of // the main chain without including fully spent transactions in the // results. Fully spent transactions are only needed for chain // reorganization which does not apply here. txStore := fetchTxStoreMain(b.db, txNeededSet, false) topBlock, err := b.getBlockFromHash(b.bestChain.hash) if err != nil { return nil, err } if isValid { connectTxTree(txStore, topBlock, true) } return txStore, nil }
// voteVersionsInBlock returns all versions in a block. func voteVersionsInBlock(bl *dcrutil.Block, params *chaincfg.Params) []uint32 { versions := make([]uint32, 0, params.TicketsPerBlock) for _, stx := range bl.MsgBlock().STransactions { if is, _ := stake.IsSSGen(stx); !is { continue } versions = append(versions, stake.SSGenVersion(stx)) } return versions }
// FetchUtxoView loads utxo details about the input transactions referenced by // the passed transaction from the point of view of the end of the main chain. // It also attempts to fetch the utxo details for the transaction itself so the // returned view can be examined for duplicate unspent transaction outputs. // // This function is safe for concurrent access however the returned view is NOT. func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, treeValid bool) (*UtxoViewpoint, error) { b.chainLock.RLock() defer b.chainLock.RUnlock() // Request the utxos from the point of view of the end of the main // chain. view := NewUtxoViewpoint() if treeValid { view.SetStakeViewpoint(ViewpointPrevValidRegular) block, err := b.fetchBlockFromHash(&b.bestNode.hash) if err != nil { return nil, err } parent, err := b.fetchBlockFromHash(&b.bestNode.header.PrevBlock) if err != nil { return nil, err } err = view.fetchInputUtxos(b.db, block, parent) if err != nil { return nil, err } for i, blockTx := range block.Transactions() { err := view.connectTransaction(blockTx, b.bestNode.height, uint32(i), nil) if err != nil { return nil, err } } } view.SetBestHash(&b.bestNode.hash) // Create a set of needed transactions based on those referenced by the // inputs of the passed transaction. Also, add the passed transaction // itself as a way for the caller to detect duplicates that are not // fully spent. txNeededSet := make(map[chainhash.Hash]struct{}) txNeededSet[*tx.Sha()] = struct{}{} msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) if !IsCoinBaseTx(msgTx) { for i, txIn := range msgTx.TxIn { if isSSGen && i == 0 { continue } txNeededSet[txIn.PreviousOutPoint.Hash] = struct{}{} } } err := view.fetchUtxosMain(b.db, txNeededSet) return view, err }
// CalculateAddedSubsidy calculates the amount of subsidy added by a block // and its parent. The blocks passed to this function MUST be valid blocks // that have already been confirmed to abide by the consensus rules of the // network, or the function might panic. func CalculateAddedSubsidy(block, parent *dcrutil.Block) int64 { var subsidy int64 regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { subsidy += parent.MsgBlock().Transactions[0].TxIn[0].ValueIn } for _, stx := range block.MsgBlock().STransactions { if isSSGen, _ := stake.IsSSGen(stx); isSSGen { subsidy += stx.TxIn[0].ValueIn } } return subsidy }
// makeUtxoView creates a mock unspent transaction output view by using the // transaction index in order to look up all inputs referenced by the // transactions in the block. This is sometimes needed when catching indexes up // because many of the txouts could actually already be spent however the // associated scripts are still required to index them. func makeUtxoView(dbTx database.Tx, block, parent *dcrutil.Block) (*blockchain.UtxoViewpoint, error) { view := blockchain.NewUtxoViewpoint() regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx == 0 { continue } // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for _, txIn := range tx.MsgTx().TxIn { // Skip already fetched outputs. originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } } for _, tx := range block.STransactions() { msgTx := tx.MsgTx() isSSGen, _ := stake.IsSSGen(msgTx) // Use the transaction index to load all of the referenced // inputs and add their outputs to the view. for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } originOut := &txIn.PreviousOutPoint if view.LookupEntry(&originOut.Hash) != nil { continue } originTx, err := dbFetchTx(dbTx, originOut.Hash) if err != nil { return nil, err } view.AddTxOuts(dcrutil.NewTx(originTx), int64(wire.NullBlockHeight), wire.NullBlockIndex) } } return view, nil }
// DebugMsgTxString dumps a verbose message containing information about the // contents of a transaction. func DebugMsgTxString(msgTx *wire.MsgTx) string { tx := dcrutil.NewTx(msgTx) isSStx, _ := stake.IsSStx(tx) isSSGen, _ := stake.IsSSGen(tx) var sstxType []bool var sstxPkhs [][]byte var sstxAmts []int64 var sstxRules [][]bool var sstxLimits [][]uint16 if isSStx { sstxType, sstxPkhs, sstxAmts, _, sstxRules, sstxLimits = stake.GetSStxStakeOutputInfo(tx) } var buffer bytes.Buffer hash := msgTx.TxSha() str := fmt.Sprintf("Transaction hash: %v, Version %v, Locktime: %v, "+ "Expiry %v\n\n", hash, msgTx.Version, msgTx.LockTime, msgTx.Expiry) buffer.WriteString(str) str = fmt.Sprintf("==INPUTS==\nNumber of inputs: %v\n\n", len(msgTx.TxIn)) buffer.WriteString(str) for i, input := range msgTx.TxIn { str = fmt.Sprintf("Input number: %v\n", i) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint hash: %v, ", input.PreviousOutPoint.Hash) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint index: %v, ", input.PreviousOutPoint.Index) buffer.WriteString(str) str = fmt.Sprintf("Previous outpoint tree: %v \n", input.PreviousOutPoint.Tree) buffer.WriteString(str) str = fmt.Sprintf("Sequence: %v \n", input.Sequence) buffer.WriteString(str) str = fmt.Sprintf("ValueIn: %v \n", input.ValueIn) buffer.WriteString(str) str = fmt.Sprintf("BlockHeight: %v \n", input.BlockHeight) buffer.WriteString(str) str = fmt.Sprintf("BlockIndex: %v \n", input.BlockIndex) buffer.WriteString(str) str = fmt.Sprintf("Raw signature script: %x \n", input.SignatureScript) buffer.WriteString(str) sigScr, _ := txscript.DisasmString(input.SignatureScript) str = fmt.Sprintf("Disasmed signature script: %v \n\n", sigScr) buffer.WriteString(str) } str = fmt.Sprintf("==OUTPUTS==\nNumber of outputs: %v\n\n", len(msgTx.TxOut)) buffer.WriteString(str) for i, output := range msgTx.TxOut { str = fmt.Sprintf("Output number: %v\n", i) buffer.WriteString(str) coins := float64(output.Value) / 1e8 str = fmt.Sprintf("Output amount: %v atoms or %v coins\n", output.Value, coins) buffer.WriteString(str) // SStx OP_RETURNs, dump pkhs and amts committed if isSStx && i != 0 && i%2 == 1 { coins := float64(sstxAmts[i/2]) / 1e8 str = fmt.Sprintf("SStx commit amount: %v atoms or %v coins\n", sstxAmts[i/2], coins) buffer.WriteString(str) str = fmt.Sprintf("SStx commit address: %x\n", sstxPkhs[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx address type is P2SH: %v\n", sstxType[i/2]) buffer.WriteString(str) str = fmt.Sprintf("SStx all address types is P2SH: %v\n", sstxType) buffer.WriteString(str) str = fmt.Sprintf("Voting is fee limited: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) if sstxRules[i/2][0] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][0]) buffer.WriteString(str) } str = fmt.Sprintf("Revoking is fee limited: %v\n", sstxRules[i/2][1]) buffer.WriteString(str) if sstxRules[i/2][1] { str = fmt.Sprintf("Voting limit imposed: %v\n", sstxLimits[i/2][1]) buffer.WriteString(str) } } // SSGen block/block height OP_RETURN. if isSSGen && i == 0 { blkHash, blkHeight, _ := stake.GetSSGenBlockVotedOn(tx) str = fmt.Sprintf("SSGen block hash voted on: %v, height: %v\n", blkHash, blkHeight) buffer.WriteString(str) } if isSSGen && i == 1 { vb := stake.GetSSGenVoteBits(tx) str = fmt.Sprintf("SSGen vote bits: %v\n", vb) buffer.WriteString(str) } str = fmt.Sprintf("Raw script: %x \n", output.PkScript) buffer.WriteString(str) scr, _ := txscript.DisasmString(output.PkScript) str = fmt.Sprintf("Disasmed script: %v \n\n", scr) buffer.WriteString(str) } return buffer.String() }
// fetchInputTransactions fetches the input transactions referenced by the // transactions in the given block from its point of view. See fetchTxList // for more details on what the point of view entails. // Decred: This function is for verifying the validity of the regular tx tree in // this block for the case that it does get accepted in the next block. func (b *BlockChain) fetchInputTransactions(node *blockNode, block *dcrutil.Block, viewpoint int8) (TxStore, error) { // Verify we have the same node as we do block. blockHash := block.Sha() if !node.hash.IsEqual(blockHash) { return nil, fmt.Errorf("node and block hash are different!") } // If we need the previous block, grab it. var parentBlock *dcrutil.Block if viewpoint == ViewpointPrevValidInitial || viewpoint == ViewpointPrevValidStake || viewpoint == ViewpointPrevValidRegular { var errFetchBlock error parentBlock, errFetchBlock = b.getBlockFromHash(node.parentHash) if errFetchBlock != nil { return nil, errFetchBlock } } txInFlight := map[chainhash.Hash]int{} txNeededSet := make(map[chainhash.Hash]struct{}) txStore := make(TxStore) // Case 1: ViewpointPrevValidInitial. We need the viewpoint of the // current chain without the TxTreeRegular of the previous block // added so we can validate that. if viewpoint == ViewpointPrevValidInitial { // Build a map of in-flight transactions because some of the inputs in // this block could be referencing other transactions earlier in this // block which are not yet in the chain. transactions := parentBlock.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD // It is acceptable for a transaction input to reference // the output of another transaction in this block only // if the referenced transaction comes before the // current one in this block. Update the transaction // store acccordingly when this is the case. Otherwise, // we still need the transaction. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] txD.Tx = originTx txD.BlockHeight = node.height - 1 txD.BlockIndex = uint32(inFlightIndex) txD.Spent = make([]bool, len(originTx.MsgTx().TxOut)) txD.Err = nil } else { txNeededSet[*originHash] = struct{}{} } } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } // Merge the results of the requested transactions and the in-flight // transactions. for _, txD := range txNeededStore { txStore[*txD.Hash] = txD } return txStore, nil } // Case 2+3: ViewpointPrevValidStake and ViewpointPrevValidStake. // For ViewpointPrevValidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // added so we can validate the TxTreeStake of the current block. // For ViewpointPrevInvalidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing so we can validate the TxTreeStake of the current block. if viewpoint == ViewpointPrevValidStake || viewpoint == ViewpointPrevInvalidStake { // We need all of the stake tx txins. None of these are considered // in-flight in relation to the regular tx tree or to other tx in // the stake tx tree, so don't do any of those expensive checks and // just append it to the tx slice. stransactions := block.STransactions() for _, tx := range stransactions { isSSGen, _ := stake.IsSSGen(tx) for i, txIn := range tx.MsgTx().TxIn { // Ignore stakebases. if isSSGen && i == 0 { continue } // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD txNeededSet[*originHash] = struct{}{} } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } return txNeededStore, nil } // Case 4+5: ViewpointPrevValidRegular and // ViewpointPrevInvalidRegular. // For ViewpointPrevValidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // and the TxTreeStake of the current block added so we can // validate the TxTreeRegular of the current block. // For ViewpointPrevInvalidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing and the TxTreeStake of the current block added so we // can validate the TxTreeRegular of the current block. if viewpoint == ViewpointPrevValidRegular || viewpoint == ViewpointPrevInvalidRegular { transactions := block.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). txNeededSet := make(map[chainhash.Hash]struct{}) txStore = make(TxStore) for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash txD := &TxData{Hash: originHash, Err: database.ErrTxShaMissing} txStore[*originHash] = txD // It is acceptable for a transaction input to reference // the output of another transaction in this block only // if the referenced transaction comes before the // current one in this block. Update the transaction // store acccordingly when this is the case. Otherwise, // we still need the transaction. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] txD.Tx = originTx txD.BlockHeight = node.height txD.BlockIndex = uint32(inFlightIndex) txD.Spent = make([]bool, len(originTx.MsgTx().TxOut)) txD.Err = nil } else { txNeededSet[*originHash] = struct{}{} } } } // Request the input transactions from the point of view of the node. txNeededStore, err := b.fetchTxStore(node, block, txNeededSet, viewpoint) if err != nil { return nil, err } // Merge the results of the requested transactions and the in-flight // transactions. for _, txD := range txNeededStore { txStore[*txD.Hash] = txD } return txStore, nil } return nil, fmt.Errorf("Invalid viewpoint passed to fetchInputTransactions") }
func (w *Wallet) addRelevantTx(dbtx walletdb.ReadWriteTx, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey) txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // should either be one or more relevant inputs or outputs. // // TODO This function is pretty bad corruption wise, it's very easy // to corrupt the wallet if you ctrl+c while in this function. This // needs desperate refactoring. tx := dcrutil.NewTx(&rec.MsgTx) txHash := rec.Hash // Handle incoming SStx; store them in the stake manager if we own // the OP_SSTX tagged out, except if we're operating as a stake pool // server. In that case, additionally consider the first commitment // output as well. if is, _ := stake.IsSStx(&rec.MsgTx); is { // Errors don't matter here. If addrs is nil, the range below // does nothing. txOut := tx.MsgTx().TxOut[0] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addrmgrNs, addr) if err == nil { // We own the voting output pubkey or script and we're // not operating as a stake pool, so simply insert this // ticket now. if !w.stakePoolEnabled { insert = true break } else { // We are operating as a stake pool. The below // function will ONLY add the ticket into the // stake pool if it has been found within a // block. if block == nil { break } valid, errEval := w.evaluateStakePoolTicket(rec, block, addr) if valid { // Be sure to insert this into the user's stake // pool entry into the stake manager. poolTicket := &wstakemgr.PoolTicket{ Ticket: txHash, HeightTicket: uint32(block.Height), Status: wstakemgr.TSImmatureOrLive, } errUpdate := w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, addr, poolTicket) if errUpdate != nil { log.Warnf("Failed to insert stake pool "+ "user ticket: %s", err.Error()) } log.Debugf("Inserted stake pool ticket %v for user %v "+ "into the stake store database", txHash, addr) insert = true break } // Log errors if there were any. At this point the ticket // must be invalid, so insert it into the list of invalid // user tickets. if errEval != nil { log.Warnf("Ticket %v failed ticket evaluation for "+ "the stake pool: %s", rec.Hash, err.Error()) } errUpdate := w.StakeMgr.UpdateStakePoolUserInvalTickets( stakemgrNs, addr, &rec.Hash) if errUpdate != nil { log.Warnf("Failed to update pool user %v with "+ "invalid ticket %v", addr.EncodeAddress(), rec.Hash) } } } } if insert { err := w.StakeMgr.InsertSStx(stakemgrNs, tx, w.VoteBits) if err != nil { log.Errorf("Failed to insert SStx %v"+ "into the stake store.", tx.Sha()) } } } // Handle incoming SSGen; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSGen(&rec.MsgTx); is { if block != nil { txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSGen(stakemgrNs, &block.Hash, int64(block.Height), &txHash, w.VoteBits.Bits, &txInHash) } // If we're running as a stake pool, insert // the stake pool user ticket update too. if w.stakePoolEnabled { txInHeight := tx.MsgTx().TxIn[1].BlockHeight poolTicket := &wstakemgr.PoolTicket{ Ticket: txInHash, HeightTicket: txInHeight, Status: wstakemgr.TSVoted, SpentBy: txHash, HeightSpent: uint32(block.Height), } poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash) if err != nil { log.Warnf("Failed to fetch stake pool user for "+ "ticket %v (voted ticket)", txInHash) } else { err = w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, poolUser, poolTicket) if err != nil { log.Warnf("Failed to update stake pool ticket for "+ "stake pool user %s after voting", poolUser.EncodeAddress()) } else { log.Debugf("Updated voted stake pool ticket %v "+ "for user %v into the stake store database ("+ "vote hash: %v)", txInHash, poolUser, txHash) } } } } else { // If there's no associated block, it's potentially a // doublespent SSGen. Just ignore it and wait for it // to later get into a block. return nil } } // Handle incoming SSRtx; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSRtx(&rec.MsgTx); is { if block != nil { txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSRtx(stakemgrNs, &block.Hash, int64(block.Height), &txHash, &txInHash) } // If we're running as a stake pool, insert // the stake pool user ticket update too. if w.stakePoolEnabled { txInHeight := tx.MsgTx().TxIn[0].BlockHeight poolTicket := &wstakemgr.PoolTicket{ Ticket: txInHash, HeightTicket: txInHeight, Status: wstakemgr.TSMissed, SpentBy: txHash, HeightSpent: uint32(block.Height), } poolUser, err := w.StakeMgr.SStxAddress(stakemgrNs, &txInHash) if err != nil { log.Warnf("failed to fetch stake pool user for "+ "ticket %v (missed ticket)", txInHash) } else { err = w.StakeMgr.UpdateStakePoolUserTickets( stakemgrNs, addrmgrNs, poolUser, poolTicket) if err != nil { log.Warnf("failed to update stake pool ticket for "+ "stake pool user %s after revoking", poolUser.EncodeAddress()) } else { log.Debugf("Updated missed stake pool ticket %v "+ "for user %v into the stake store database ("+ "revocation hash: %v)", txInHash, poolUser, txHash) } } } } } err := w.TxStore.InsertTx(txmgrNs, addrmgrNs, rec, block) if err != nil { return err } // Handle input scripts that contain P2PKs that we care about. for i, input := range rec.MsgTx.TxIn { if txscript.IsMultisigSigScript(input.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( input.SignatureScript) if err != nil { return err } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } isRelevant := false for _, addr := range addrs { _, err := w.Manager.Address(addrmgrNs, addr) if err == nil { isRelevant = true err = w.Manager.MarkUsed(addrmgrNs, addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) } else { // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } // Add the script to the script databases. // TODO Markused script address? cj if isRelevant { err = w.TxStore.InsertTxScript(txmgrNs, rs) if err != nil { return err } var blockToUse *waddrmgr.BlockStamp if block != nil { blockToUse = &waddrmgr.BlockStamp{ Height: block.Height, Hash: block.Hash, } } mscriptaddr, err := w.Manager.ImportScript(addrmgrNs, rs, blockToUse) if err != nil { switch { // Don't care if it's already there. case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress): break case waddrmgr.IsError(err, waddrmgr.ErrLocked): log.Warnf("failed to attempt script importation "+ "of incoming tx script %x because addrmgr "+ "was locked", rs) break default: return err } } else { // This is the first time seeing this script address // belongs to us, so do a rescan and see if there are // any other outputs to this address. job := &RescanJob{ Addrs: []dcrutil.Address{mscriptaddr.Address()}, OutPoints: nil, BlockStamp: waddrmgr.BlockStamp{ Height: 0, Hash: *w.chainParams.GenesisHash, }, } // Submit rescan job and log when the import has completed. // Do not block on finishing the rescan. The rescan success // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) } } // If we're spending a multisig outpoint we know about, // update the outpoint. Inefficient because you deserialize // the entire multisig output info. Consider a specific // exists function in wtxmgr. The error here is skipped // because the absence of an multisignature output for // some script can not always be considered an error. For // example, the wallet might be rescanning as called from // the above function and so does not have the output // included yet. mso, err := w.TxStore.GetMultisigOutput(txmgrNs, &input.PreviousOutPoint) if mso != nil && err == nil { w.TxStore.SpendMultisigOut(txmgrNs, &input.PreviousOutPoint, rec.Hash, uint32(i)) } } } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { // Ignore unspendable outputs. if output.Value == 0 { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } isStakeType := class == txscript.StakeSubmissionTy || class == txscript.StakeSubChangeTy || class == txscript.StakeGenTy || class == txscript.StakeRevocationTy if isStakeType { class, err = txscript.GetStakeOutSubclass(output.PkScript) if err != nil { log.Errorf("Unknown stake output subclass parse error "+ "encountered: %v", err) continue } } for _, addr := range addrs { ma, err := w.Manager.Address(addrmgrNs, addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. err = w.TxStore.AddCredit(txmgrNs, rec, block, uint32(i), ma.Internal(), ma.Account()) if err != nil { return err } err = w.Manager.MarkUsed(addrmgrNs, addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } // Handle P2SH addresses that are multisignature scripts // with keys that we own. if class == txscript.ScriptHashTy { var expandedScript []byte for _, addr := range addrs { // Search both the script store in the tx store // and the address manager for the redeem script. var err error expandedScript, err = w.TxStore.GetTxScript(txmgrNs, addr.ScriptAddress()) if err != nil { return err } if expandedScript == nil { scrAddr, err := w.Manager.Address(addrmgrNs, addr) if err == nil { sa, ok := scrAddr.(waddrmgr.ManagedScriptAddress) if !ok { log.Warnf("address %v is not a script"+ " address (type %T)", scrAddr.Address().EncodeAddress(), scrAddr.Address()) continue } retrievedScript, err := sa.Script() if err != nil { log.Errorf("failed to decode redeemscript for "+ "address %v: %v", addr.EncodeAddress(), err.Error()) continue } expandedScript = retrievedScript } else { // We can't find this redeem script anywhere. // Skip this output. log.Debugf("failed to find redeemscript for "+ "address %v in address manager: %v", addr.EncodeAddress(), err.Error()) continue } } } // Otherwise, extract the actual addresses and // see if any belong to us. expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, expandedScript, w.chainParams) if err != nil { return err } // Skip non-multisig scripts. if expClass != txscript.MultiSigTy { continue } for _, maddr := range multisigAddrs { _, err := w.Manager.Address(addrmgrNs, maddr) // An address we own; handle accordingly. if err == nil { errStore := w.TxStore.AddMultisigOut( txmgrNs, rec, block, uint32(i)) if errStore != nil { // This will throw if there are multiple private keys // for this multisignature output owned by the wallet, // so it's routed to debug. log.Debugf("unable to add multisignature output: %v", errStore.Error()) } } } } } // Send notification of mined or unmined transaction to any interested // clients. // // TODO: Avoid the extra db hits. if block == nil { details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, nil) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { w.NtfnServer.notifyUnminedTransaction(dbtx, details) } } else { details, err := w.TxStore.UniqueTxDetails(txmgrNs, &rec.Hash, &block.Block) if err != nil { log.Errorf("Cannot query transaction details for notifiation: %v", err) } else { w.NtfnServer.notifyMinedTransaction(dbtx, details, block) } } return nil }
// generateVote creates a new SSGen given a header hash, height, sstx // tx hash, and votebits. func (s *StakeStore) generateVote(ns walletdb.ReadWriteBucket, waddrmgrNs walletdb.ReadBucket, blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash, defaultVoteBits stake.VoteBits, allowHighFees bool) (*StakeNotification, error) { // 1. Fetch the SStx, then calculate all the values we'll need later for // the generation of the SSGen tx outputs. sstxRecord, err := s.getSStx(ns, sstxHash) if err != nil { return nil, err } sstx := sstxRecord.tx sstxMsgTx := sstx.MsgTx() // The legacy wallet didn't store anything about the voteBits to use. // In the case we're loading a legacy wallet and the voteBits are // unset, just use the default voteBits as set by the user. voteBits := defaultVoteBits if sstxRecord.voteBitsSet { voteBits.Bits = sstxRecord.voteBits voteBits.ExtendedBits = sstxRecord.voteBitsExt } // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. // TODO Get information on the allowable fee range for the vote // and check to make sure we don't overflow that. ssgenPayTypes, ssgenPkhs, sstxAmts, _, _, _ := stake.TxSStxStakeOutputInfo(sstxMsgTx) // Get the current reward. initSudsidyCacheOnce.Do(func() { subsidyCache = blockchain.NewSubsidyCache(height, s.Params) }) stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, height, s.Params) // Calculate the output values from this data. ssgenCalcAmts := stake.CalculateRewards(sstxAmts, sstxMsgTx.TxOut[0].Value, stakeVoteSubsidy) subsidyCache = blockchain.NewSubsidyCache(height, s.Params) // 2. Add all transaction inputs to a new transaction after performing // some validity checks. First, add the stake base, then the OP_SSTX // tagged output. msgTx := wire.NewMsgTx() // Stakebase. stakeBaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, uint32(0xFFFFFFFF), wire.TxTreeRegular) txInStakeBase := wire.NewTxIn(stakeBaseOutPoint, []byte{}) msgTx.AddTxIn(txInStakeBase) // Add the subsidy amount into the input. msgTx.TxIn[0].ValueIn = stakeVoteSubsidy // SStx tagged output as an OutPoint. prevOut := wire.NewOutPoint(sstxHash, 0, // Index 0 1) // Tree stake txIn := wire.NewTxIn(prevOut, []byte{}) msgTx.AddTxIn(txIn) // 3. Add the OP_RETURN null data pushes of the block header hash, // the block height, and votebits, then add all the OP_SSGEN tagged // outputs. // // Block reference output. blockRefScript, err := txscript.GenerateSSGenBlockRef(*blockHash, uint32(height)) if err != nil { return nil, err } blockRefOut := wire.NewTxOut(0, blockRefScript) msgTx.AddTxOut(blockRefOut) // Votebits output. blockVBScript, err := generateVoteScript(voteBits) if err != nil { return nil, err } blockVBOut := wire.NewTxOut(0, blockVBScript) msgTx.AddTxOut(blockVBOut) // Add all the SSGen-tagged transaction outputs to the transaction after // performing some validity checks. for i, ssgenPkh := range ssgenPkhs { // Create a new script which pays to the provided address specified in // the original ticket tx. var ssgenOutScript []byte switch ssgenPayTypes[i] { case false: // P2PKH ssgenOutScript, err = txscript.PayToSSGenPKHDirect(ssgenPkh) if err != nil { return nil, err } case true: // P2SH ssgenOutScript, err = txscript.PayToSSGenSHDirect(ssgenPkh) if err != nil { return nil, err } } // Add the txout to our SSGen tx. txOut := wire.NewTxOut(ssgenCalcAmts[i], ssgenOutScript) msgTx.AddTxOut(txOut) } // Check to make sure our SSGen was created correctly. _, err = stake.IsSSGen(msgTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(waddrmgrNs, msgTx, sstx, true) if err != nil { return nil, err } // Store the information about the SSGen. hash := msgTx.TxSha() err = s.insertSSGen(ns, blockHash, height, &hash, voteBits.Bits, sstx.Sha()) if err != nil { return nil, err } // Send the transaction. ssgenSha, err := s.chainSvr.SendRawTransaction(msgTx, allowHighFees) if err != nil { return nil, err } log.Debugf("Generated SSGen %v, voting on block %v at height %v. "+ "The ticket used to generate the SSGen was %v.", ssgenSha, blockHash, height, sstxHash) // Generate a notification to return. ntfn := &StakeNotification{ TxType: int8(stake.TxTypeSSGen), TxHash: *ssgenSha, BlockHash: *blockHash, Height: int32(height), Amount: 0, SStxIn: *sstx.Sha(), VoteBits: voteBits.Bits, } return ntfn, nil }
// indexBlock extract all of the standard addresses from all of the transactions // in the passed block and maps each of them to the assocaited transaction using // the passed map. func (idx *AddrIndex) indexBlock(data writeIndexData, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var stakeStartIdx int if regularTxTreeValid { for txIdx, tx := range parent.Transactions() { // Coinbases do not reference any inputs. Since the block is // required to have already gone through full validation, it has // already been proven on the first transaction in the block is // a coinbase. if txIdx != 0 { for _, txIn := range tx.MsgTx().TxIn { // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, txIdx, txType == stake.TxTypeSStx) } } for _, txOut := range tx.MsgTx().TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, txIdx, false) } } stakeStartIdx = len(parent.Transactions()) } for txIdx, tx := range block.STransactions() { msgTx := tx.MsgTx() thisTxOffset := txIdx + stakeStartIdx isSSGen, _ := stake.IsSSGen(msgTx) for i, txIn := range msgTx.TxIn { // Skip stakebases. if isSSGen && i == 0 { continue } // The view should always have the input since // the index contract requires it, however, be // safe and simply ignore any missing entries. origin := &txIn.PreviousOutPoint entry := view.LookupEntry(&origin.Hash) if entry == nil { log.Warnf("Missing input %v for tx %v while "+ "indexing block %v (height %v)\n", origin.Hash, tx.Sha(), block.Sha(), block.Height()) continue } version := entry.ScriptVersionByIndex(origin.Index) pkScript := entry.PkScriptByIndex(origin.Index) txType := entry.TransactionType() idx.indexPkScript(data, version, pkScript, thisTxOffset, txType == stake.TxTypeSStx) } isSStx, _ := stake.IsSStx(msgTx) for _, txOut := range msgTx.TxOut { idx.indexPkScript(data, txOut.Version, txOut.PkScript, thisTxOffset, isSStx) } } }
// connectTickets updates the passed map by removing removing any tickets // from the ticket pool that have been considered spent or missed in this block // according to the block header. Then, it connects all the newly mature tickets // to the passed map. func (b *BlockChain) connectTickets(tixStore TicketStore, node *blockNode, block *dcrutil.Block) error { if tixStore == nil { return fmt.Errorf("nil ticket store!") } // Nothing to do if tickets haven't yet possibly matured. height := node.height if height < b.chainParams.StakeEnabledHeight { return nil } parentBlock, err := b.GetBlockFromHash(node.parentHash) if err != nil { return err } revocations := node.header.Revocations tM := int64(b.chainParams.TicketMaturity) // Skip a number of validation steps before we requiring chain // voting. if node.height >= b.chainParams.StakeValidationHeight { regularTxTreeValid := dcrutil.IsFlagSet16(node.header.VoteBits, dcrutil.BlockValid) thisNodeStakeViewpoint := ViewpointPrevInvalidStake if regularTxTreeValid { thisNodeStakeViewpoint = ViewpointPrevValidStake } // We need the missed tickets bucket from the original perspective of // the node. missedTickets, err := b.GenerateMissedTickets(tixStore) if err != nil { return err } // TxStore at blockchain HEAD + TxTreeRegular of prevBlock (if // validated) for this node. txInputStoreStake, err := b.fetchInputTransactions(node, block, thisNodeStakeViewpoint) if err != nil { errStr := fmt.Sprintf("fetchInputTransactions failed for incoming "+ "node %v; error given: %v", node.hash, err) return errors.New(errStr) } // PART 1: Spend/miss winner tickets // Iterate through all the SSGen (vote) tx in the block and add them to // a map of tickets that were actually used. spentTicketsFromBlock := make(map[chainhash.Hash]bool) numberOfSSgen := 0 for _, staketx := range block.STransactions() { if is, _ := stake.IsSSGen(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[1] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash originTx, exists := txInputStoreStake[sstxHash] if !exists { str := fmt.Sprintf("unable to find input transaction "+ "%v for transaction %v", sstxHash, staketx.Sha()) return ruleError(ErrMissingTx, str) } sstxHeight := originTx.BlockHeight // Check maturity of ticket; we can only spend the ticket after it // hits maturity at height + tM + 1. if (height - sstxHeight) < (tM + 1) { blockSha := block.Sha() errStr := fmt.Sprintf("Error: A ticket spend as an SSGen in "+ "block height %v was immature! Block sha %v", height, blockSha) return errors.New(errStr) } // Fill out the ticket data. spentTicketsFromBlock[sstxHash] = true numberOfSSgen++ } } // Obtain the TicketsPerBlock many tickets that were selected this round, // then check these against the tickets that were actually used to make // sure that any SSGen actually match the selected tickets. Commit the // spent or missed tickets to the ticket store after. spentAndMissedTickets := make(TicketStore) tixSpent := 0 tixMissed := 0 // Sort the entire list of tickets lexicographically by sorting // each bucket and then appending it to the list. Start by generating // a prefix matched map of tickets to speed up the lookup. tpdBucketMap := make(map[uint8][]*TicketPatchData) for _, tpd := range tixStore { // Bucket does not exist. if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok { tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1) tpdBucketMap[tpd.td.Prefix][0] = tpd } else { // Bucket exists. data := tpdBucketMap[tpd.td.Prefix] tpdBucketMap[tpd.td.Prefix] = append(data, tpd) } } totalTickets := 0 sortedSlice := make([]*stake.TicketData, 0) for i := 0; i < stake.BucketsSize; i++ { ltb, err := b.GenerateLiveTicketBucket(tixStore, tpdBucketMap, uint8(i)) if err != nil { h := node.hash str := fmt.Sprintf("Failed to generate live ticket bucket "+ "%v for node %v, height %v! Error: %v", i, h, node.height, err.Error()) return fmt.Errorf(str) } mapLen := len(ltb) tempTdSlice := stake.NewTicketDataSlice(mapLen) itr := 0 // Iterator for _, td := range ltb { tempTdSlice[itr] = td itr++ totalTickets++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } // Use the parent block's header to seed a PRNG that picks the // lottery winners. ticketsPerBlock := int(b.chainParams.TicketsPerBlock) pbhB, err := parentBlock.MsgBlock().Header.Bytes() if err != nil { return err } prng := stake.NewHash256PRNG(pbhB) ts, err := stake.FindTicketIdxs(int64(totalTickets), ticketsPerBlock, prng) if err != nil { return err } ticketsToSpendOrMiss := make([]*stake.TicketData, ticketsPerBlock, ticketsPerBlock) for i, idx := range ts { ticketsToSpendOrMiss[i] = sortedSlice[idx] } // Spend or miss these tickets by checking for their existence in the // passed spentTicketsFromBlock map. for _, ticket := range ticketsToSpendOrMiss { // Move the ticket from active tickets map into the used tickets // map if the ticket was spent. wasSpent, _ := spentTicketsFromBlock[ticket.SStxHash] if wasSpent { tpd := NewTicketPatchData(ticket, TiSpent, nil) spentAndMissedTickets[ticket.SStxHash] = tpd tixSpent++ } else { // Ticket missed being spent and --> false or nil tpd := NewTicketPatchData(ticket, TiMissed, nil) spentAndMissedTickets[ticket.SStxHash] = tpd tixMissed++ } } // This error is thrown if for some reason there exists an SSGen in // the block that doesn't spend a ticket from the eligible list of // tickets, thus making it invalid. if tixSpent != numberOfSSgen { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was spent, but %v many tickets should "+ "have been spent!", tixSpent, numberOfSSgen) return errors.New(errStr) } if tixMissed != (ticketsPerBlock - numberOfSSgen) { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was missed, but %v many tickets should "+ "have been missed!", tixMissed, ticketsPerBlock-numberOfSSgen) return errors.New(errStr) } if (tixSpent + tixMissed) != int(b.chainParams.TicketsPerBlock) { errStr := fmt.Sprintf("an invalid number %v "+ "tickets was spent and missed, but TicketsPerBlock %v many "+ "tickets should have been spent!", tixSpent, ticketsPerBlock) return errors.New(errStr) } // Calculate all the tickets expiring this block and mark them as missed. tpdBucketMap = make(map[uint8][]*TicketPatchData) for _, tpd := range tixStore { // Bucket does not exist. if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok { tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1) tpdBucketMap[tpd.td.Prefix][0] = tpd } else { // Bucket exists. data := tpdBucketMap[tpd.td.Prefix] tpdBucketMap[tpd.td.Prefix] = append(data, tpd) } } toExpireHeight := node.height - int64(b.chainParams.TicketExpiry) if !(toExpireHeight < int64(b.chainParams.StakeEnabledHeight)) { for i := 0; i < stake.BucketsSize; i++ { // Generate the live ticket bucket. ltb, err := b.GenerateLiveTicketBucket(tixStore, tpdBucketMap, uint8(i)) if err != nil { return err } for _, ticket := range ltb { if ticket.BlockHeight == toExpireHeight { tpd := NewTicketPatchData(ticket, TiMissed, nil) spentAndMissedTickets[ticket.SStxHash] = tpd } } } } // Merge the ticket store patch containing the spent and missed tickets // with the ticket store. for hash, tpd := range spentAndMissedTickets { tixStore[hash] = tpd } // At this point our tixStore now contains all the spent and missed tx // as per this block. // PART 2: Remove tickets that were missed and are now revoked. // Iterate through all the SSGen (vote) tx in the block and add them to // a map of tickets that were actually used. revocationsFromBlock := make(map[chainhash.Hash]struct{}) numberOfSSRtx := 0 for _, staketx := range block.STransactions() { if is, _ := stake.IsSSRtx(staketx); is { msgTx := staketx.MsgTx() sstxIn := msgTx.TxIn[0] // sstx input sstxHash := sstxIn.PreviousOutPoint.Hash // Fill out the ticket data. revocationsFromBlock[sstxHash] = struct{}{} numberOfSSRtx++ } } if numberOfSSRtx != int(revocations) { errStr := fmt.Sprintf("an invalid revocations %v was calculated "+ "the block header indicates %v instead", numberOfSSRtx, revocations) return errors.New(errStr) } // Lookup the missed ticket. If we find it in the patch data, // modify the patch data so that it doesn't exist. // Otherwise, just modify load the missed ticket data from // the ticket db and create patch data based on that. for hash, _ := range revocationsFromBlock { ticketWasMissed := false if td, is := missedTickets[hash]; is { maturedHeight := td.BlockHeight // Check maturity of ticket; we can only spend the ticket after it // hits maturity at height + tM + 2. if height < maturedHeight+2 { blockSha := block.Sha() errStr := fmt.Sprintf("Error: A ticket spend as an "+ "SSRtx in block height %v was immature! Block sha %v", height, blockSha) return errors.New(errStr) } ticketWasMissed = true } if !ticketWasMissed { errStr := fmt.Sprintf("SSRtx spent missed sstx %v, "+ "but that missed sstx could not be found!", hash) return errors.New(errStr) } } } // PART 3: Add newly maturing tickets // This is the only chunk we need to do for blocks appearing before // stake validation height. // Calculate block number for where new tickets are maturing from and retrieve // this block from db. // Get the block that is maturing. matureNode, err := b.getNodeAtHeightFromTopNode(node, tM) if err != nil { return err } matureBlock, errBlock := b.getBlockFromHash(matureNode.hash) if errBlock != nil { return errBlock } // Maturing tickets are from the maturingBlock; fill out the ticket patch data // and then push them to the tixStore. for _, stx := range matureBlock.STransactions() { if is, _ := stake.IsSStx(stx); is { // Calculate the prefix for pre-sort. sstxHash := *stx.Sha() prefix := uint8(sstxHash[0]) // Fill out the ticket data. td := stake.NewTicketData(sstxHash, prefix, chainhash.Hash{}, height, false, // not missed false) // not expired tpd := NewTicketPatchData(td, TiAvailable, nil) tixStore[*stx.Sha()] = tpd } } return nil }
// traceDevPremineOuts returns a list of outpoints that are part of the dev // premine coins and are ancestors of the inputs to the passed transaction hash. func traceDevPremineOuts(client *dcrrpcclient.Client, txHash *chainhash.Hash) ([]wire.OutPoint, error) { // Trace the lineage of all inputs to the provided transaction back to // the coinbase outputs that generated them and add those outpoints to // a list. Also, keep track of all of the processed transactions in // order to avoid processing duplicates. knownCoinbases := make(map[chainhash.Hash]struct{}) processedHashes := make(map[chainhash.Hash]struct{}) coinbaseOuts := make([]wire.OutPoint, 0, 10) processOuts := []wire.OutPoint{{Hash: *txHash}} for len(processOuts) > 0 { // Grab the first outpoint to process and skip it if it has // already been traced. outpoint := processOuts[0] processOuts = processOuts[1:] if _, exists := processedHashes[outpoint.Hash]; exists { if _, exists := knownCoinbases[outpoint.Hash]; exists { coinbaseOuts = append(coinbaseOuts, outpoint) } continue } processedHashes[outpoint.Hash] = struct{}{} // Request the transaction for the outpoint from the server. tx, err := client.GetRawTransaction(&outpoint.Hash) if err != nil { return nil, fmt.Errorf("failed to get transaction %v: %v", &outpoint.Hash, err) } // Add the outpoint to the coinbase outputs list when it is part // of a coinbase transaction. Also, keep track of the fact the // transaction is a coinbase to use when avoiding duplicate // checks. if blockchain.IsCoinBase(tx) { knownCoinbases[outpoint.Hash] = struct{}{} coinbaseOuts = append(coinbaseOuts, outpoint) continue } // Add the inputs to the transaction to the list of transactions // to load and continue tracing. // // However, skip the first input to stake generation txns since // they are creating new coins. The remaining inputs to a // stake generation transaction still need to be traced since // they represent the coins that purchased the ticket. txIns := tx.MsgTx().TxIn isSSGen, _ := stake.IsSSGen(tx.MsgTx()) if isSSGen { txIns = txIns[1:] } for _, txIn := range txIns { processOuts = append(processOuts, txIn.PreviousOutPoint) } } // Add any of the outputs that are dev premine outputs to a list. var devPremineOuts []wire.OutPoint for _, coinbaseOut := range coinbaseOuts { if isDevPremineOut(coinbaseOut) { devPremineOuts = append(devPremineOuts, coinbaseOut) } } return devPremineOuts, nil }
// maybeAcceptBlock potentially accepts a block into the memory block chain. // It performs several validation checks which depend on its position within // the block chain before adding it. The block is expected to have already gone // through ProcessBlock before calling this function with it. // // The flags modify the behavior of this function as follows: // - BFDryRun: The memory chain index will not be pruned and no accept // notification will be sent since the block is not being accepted. func (b *BlockChain) maybeAcceptBlock(block *dcrutil.Block, flags BehaviorFlags) (bool, error) { dryRun := flags&BFDryRun == BFDryRun // Get a block node for the block previous to this one. Will be nil // if this is the genesis block. prevNode, err := b.getPrevNodeFromBlock(block) if err != nil { log.Debugf("getPrevNodeFromBlock: %v", err) return false, err } // The height of this block is one more than the referenced previous // block. blockHeight := int64(0) if prevNode != nil { blockHeight = prevNode.height + 1 } block.SetHeight(blockHeight) // The block must pass all of the validation rules which depend on the // position of the block within the block chain. err = b.checkBlockContext(block, prevNode, flags) if err != nil { return false, err } // Prune block nodes which are no longer needed before creating // a new node. if !dryRun { err = b.pruneBlockNodes() if err != nil { return false, err } } // Create a new block node for the block and add it to the in-memory // block chain (could be either a side chain or the main chain). blockHeader := &block.MsgBlock().Header var voteBitsStake []uint16 for _, stx := range block.STransactions() { if is, _ := stake.IsSSGen(stx); is { vb := stake.GetSSGenVoteBits(stx) voteBitsStake = append(voteBitsStake, vb) } } newNode := newBlockNode(blockHeader, block.Sha(), blockHeight, voteBitsStake) if prevNode != nil { newNode.parent = prevNode newNode.height = blockHeight newNode.workSum.Add(prevNode.workSum, newNode.workSum) } // Connect the passed block to the chain while respecting proper chain // selection according to the chain with the most proof of work. This // also handles validation of the transaction scripts. var onMainChain bool onMainChain, err = b.connectBestChain(newNode, block, flags) if err != nil { return false, err } // Notify the caller that the new block was accepted into the block // chain. The caller would typically want to react by relaying the // inventory to other peers. if !dryRun { b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{onMainChain, block}) } return onMainChain, nil }
// fetchInputUtxos loads utxo details about the input transactions referenced // by the transactions in the given block into the view from the database as // needed. In particular, referenced entries that are earlier in the block are // added to the view and entries that are already in the view are not modified. func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block, parent *dcrutil.Block) error { viewpoint := view.StakeViewpoint() // Build a map of in-flight transactions because some of the inputs in // this block could be referencing other transactions earlier in this // block which are not yet in the chain. txInFlight := map[chainhash.Hash]int{} txNeededSet := make(map[chainhash.Hash]struct{}) // Case 1: ViewpointPrevValidInitial. We need the viewpoint of the // current chain without the TxTreeRegular of the previous block // added so we can validate that. if viewpoint == ViewpointPrevValidInitial { transactions := parent.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // It is acceptable for a transaction input to reference // the output of another transaction in this block only // if the referenced transaction comes before the // current one in this block. Add the outputs of the // referenced transaction as available utxos when this // is the case. Otherwise, the utxo details are still // needed. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. originHash := &txIn.PreviousOutPoint.Hash if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] view.AddTxOuts(originTx, block.Height(), uint32(i)) continue } // Don't request entries that are already in the view // from the database. if _, ok := view.entries[*originHash]; ok { continue } txNeededSet[*originHash] = struct{}{} } } // Request the input utxos from the database. return view.fetchUtxosMain(db, txNeededSet) } // Case 2+3: ViewpointPrevValidStake and ViewpointPrevValidStake. // For ViewpointPrevValidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // added so we can validate the TxTreeStake of the current block. // For ViewpointPrevInvalidStake, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing so we can validate the TxTreeStake of the current block. if viewpoint == ViewpointPrevValidStake || viewpoint == ViewpointPrevInvalidStake { // We need all of the stake tx txins. None of these are considered // in-flight in relation to the regular tx tree or to other tx in // the stake tx tree, so don't do any of those expensive checks and // just append it to the tx slice. for _, tx := range block.MsgBlock().STransactions { isSSGen, _ := stake.IsSSGen(tx) for i, txIn := range tx.TxIn { // Ignore stakebases. if isSSGen && i == 0 { continue } // Add an entry to the transaction store for the needed // transaction with it set to missing by default. originHash := &txIn.PreviousOutPoint.Hash // Don't request entries that are already in the view // from the database. if _, ok := view.entries[*originHash]; ok { continue } txNeededSet[*originHash] = struct{}{} } } // Request the input utxos from the database. return view.fetchUtxosMain(db, txNeededSet) } // Case 4+5: ViewpointPrevValidRegular and // ViewpointPrevInvalidRegular. // For ViewpointPrevValidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // and the TxTreeStake of the current block added so we can // validate the TxTreeRegular of the current block. // For ViewpointPrevInvalidRegular, we need the viewpoint of the // current chain with the TxTreeRegular of the previous block // missing and the TxTreeStake of the current block added so we // can validate the TxTreeRegular of the current block. if viewpoint == ViewpointPrevValidRegular || viewpoint == ViewpointPrevInvalidRegular { transactions := block.Transactions() for i, tx := range transactions { txInFlight[*tx.Sha()] = i } // Loop through all of the transaction inputs (except for the coinbase // which has no inputs) collecting them into sets of what is needed and // what is already known (in-flight). txNeededSet := make(map[chainhash.Hash]struct{}) for i, tx := range transactions[1:] { for _, txIn := range tx.MsgTx().TxIn { // It is acceptable for a transaction input to reference // the output of another transaction in this block only // if the referenced transaction comes before the // current one in this block. Add the outputs of the // referenced transaction as available utxos when this // is the case. Otherwise, the utxo details are still // needed. // // NOTE: The >= is correct here because i is one less // than the actual position of the transaction within // the block due to skipping the coinbase. originHash := &txIn.PreviousOutPoint.Hash if inFlightIndex, ok := txInFlight[*originHash]; ok && i >= inFlightIndex { originTx := transactions[inFlightIndex] view.AddTxOuts(originTx, block.Height(), uint32(i)) continue } // Don't request entries that are already in the view // from the database. if _, ok := view.entries[*originHash]; ok { continue } txNeededSet[*originHash] = struct{}{} } } // Request the input utxos from the database. return view.fetchUtxosMain(db, txNeededSet) } // TODO actual blockchain error return fmt.Errorf("invalid stake viewpoint") }
// indexBlockAddrs returns a populated index of the all the transactions in the // passed block based on the addresses involved in each transaction. func (a *addrIndexer) indexBlockAddrs(blk *dcrutil.Block, parent *dcrutil.Block) (database.BlockAddrIndex, error) { var addrIndex database.BlockAddrIndex _, stxLocs, err := blk.TxLoc() if err != nil { return nil, err } txTreeRegularValid := dcrutil.IsFlagSet16(blk.MsgBlock().Header.VoteBits, dcrutil.BlockValid) // Add regular transactions iff the block was validated. if txTreeRegularValid { txLocs, _, err := parent.TxLoc() if err != nil { return nil, err } for txIdx, tx := range parent.Transactions() { // Tx's offset and length in the block. locInBlock := &txLocs[txIdx] // Coinbases don't have any inputs. if !blockchain.IsCoinBase(tx) { // Index the SPK's of each input's previous outpoint // transaction. for _, txIn := range tx.MsgTx().TxIn { prevOutTx, err := a.lookupTransaction( txIn.PreviousOutPoint.Hash, blk, parent) inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index] toAppend, err := convertToAddrIndex(inputOutPoint.Version, inputOutPoint.PkScript, parent.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting tx txin %v: %v", txIn.PreviousOutPoint.Hash, err) continue } addrIndex = append(addrIndex, toAppend...) } } for _, txOut := range tx.MsgTx().TxOut { toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript, parent.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting tx txout %v: %v", tx.MsgTx().TxSha(), err) continue } addrIndex = append(addrIndex, toAppend...) } } } // Add stake transactions. for stxIdx, stx := range blk.STransactions() { // Tx's offset and length in the block. locInBlock := &stxLocs[stxIdx] isSSGen, _ := stake.IsSSGen(stx) // Index the SPK's of each input's previous outpoint // transaction. for i, txIn := range stx.MsgTx().TxIn { // Stakebases don't have any inputs. if isSSGen && i == 0 { continue } // Lookup and fetch the referenced output's tx. prevOutTx, err := a.lookupTransaction( txIn.PreviousOutPoint.Hash, blk, parent) inputOutPoint := prevOutTx.TxOut[txIn.PreviousOutPoint.Index] toAppend, err := convertToAddrIndex(inputOutPoint.Version, inputOutPoint.PkScript, blk.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting stx txin %v: %v", txIn.PreviousOutPoint.Hash, err) continue } addrIndex = append(addrIndex, toAppend...) } for _, txOut := range stx.MsgTx().TxOut { toAppend, err := convertToAddrIndex(txOut.Version, txOut.PkScript, blk.Height(), locInBlock) if err != nil { adxrLog.Tracef("Error converting stx txout %v: %v", stx.MsgTx().TxSha(), err) continue } addrIndex = append(addrIndex, toAppend...) } } return addrIndex, nil }
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { // TODO: The transaction store and address manager need to be updated // together, but each operate under different namespaces and are changed // under new transactions. This is not error safe as we lose // transaction semantics. // // I'm unsure of the best way to solve this. Some possible solutions // and drawbacks: // // 1. Open write transactions here and pass the handle to every // waddrmr and wtxmgr method. This complicates the caller code // everywhere, however. // // 2. Move the wtxmgr namespace into the waddrmgr namespace, likely // under its own bucket. This entire function can then be moved // into the waddrmgr package, which updates the nested wtxmgr. // This removes some of separation between the components. // // 3. Use multiple wtxmgrs, one for each account, nested in the // waddrmgr namespace. This still provides some sort of logical // separation (transaction handling remains in another package, and // is simply used by waddrmgr), but may result in duplicate // transactions being saved if they are relevant to multiple // accounts. // // 4. Store wtxmgr-related details under the waddrmgr namespace, but // solve the drawback of #3 by splitting wtxmgr to save entire // transaction records globally for all accounts, with // credit/debit/balance tracking per account. Each account would // also save the relevant transaction hashes and block incidence so // the full transaction can be loaded from the waddrmgr // transactions bucket. This currently seems like the best // solution. // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // should either be one or more relevant inputs or outputs. // // TODO This function is pretty bad corruption wise, it's very easy // to corrupt the wallet if you ctrl+c while in this function. This // needs desperate refactoring. tx := dcrutil.NewTx(&rec.MsgTx) // Handle incoming SStx; store them in the stake manager if we own // the OP_SSTX tagged out. if is, _ := stake.IsSStx(tx); is { // Errors don't matter here. If addrs is nil, the range below // does nothing. txOut := tx.MsgTx().TxOut[0] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, w.chainParams) insert := false for _, addr := range addrs { _, err := w.Manager.Address(addr) if err == nil { insert = true break } } if insert { err := w.StakeMgr.InsertSStx(tx) if err != nil { log.Errorf("Failed to insert SStx %v"+ "into the stake store.", tx.Sha()) } } } // Handle incoming SSGen; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSGen(tx); is { if block != nil { txInHash := tx.MsgTx().TxIn[1].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSGen(&block.Hash, int64(block.Height), tx.Sha(), w.VoteBits, &txInHash) } } else { // If there's no associated block, it's potentially a // doublespent SSGen. Just ignore it and wait for it // to later get into a block. return nil } } // Handle incoming SSRtx; store them if we own // the ticket used to purchase them. if is, _ := stake.IsSSRtx(tx); is { if block != nil { txInHash := tx.MsgTx().TxIn[0].PreviousOutPoint.Hash if w.StakeMgr.CheckHashInStore(&txInHash) { w.StakeMgr.InsertSSRtx(&block.Hash, int64(block.Height), tx.Sha(), &txInHash) } } } err := w.TxStore.InsertTx(rec, block) if err != nil { return err } // Handle input scripts that contain P2PKs that we care about. for i, input := range rec.MsgTx.TxIn { if txscript.IsMultisigSigScript(input.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( input.SignatureScript) if err != nil { return err } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } isRelevant := false for _, addr := range addrs { _, err := w.Manager.Address(addr) if err == nil { isRelevant = true err = w.Manager.MarkUsed(addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) } else { // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } // Add the script to the script databases. // TODO Markused script address? cj if isRelevant { err = w.TxStore.InsertTxScript(rs) if err != nil { return err } var blockToUse *waddrmgr.BlockStamp if block != nil { blockToUse = &waddrmgr.BlockStamp{block.Height, block.Hash} } mscriptaddr, err := w.Manager.ImportScript(rs, blockToUse) if err != nil { switch { // Don't care if it's already there. case waddrmgr.IsError(err, waddrmgr.ErrDuplicateAddress): break case waddrmgr.IsError(err, waddrmgr.ErrLocked): log.Debugf("failed to attempt script importation " + "of incoming tx because addrmgr was locked") break default: return err } } else { // This is the first time seeing this script address // belongs to us, so do a rescan and see if there are // any other outputs to this address. job := &RescanJob{ Addrs: []dcrutil.Address{mscriptaddr.Address()}, OutPoints: nil, BlockStamp: waddrmgr.BlockStamp{ 0, *w.chainParams.GenesisHash, }, } // Submit rescan job and log when the import has completed. // Do not block on finishing the rescan. The rescan success // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) } } // If we're spending a multisig outpoint we // know about, update the outpoint. // Inefficient because you deserialize the // entire multisig output info, consider // a specific exists function in wtxmgr. cj mso, err := w.TxStore.GetMultisigOutput(&input.PreviousOutPoint) if err != nil { return err } if mso != nil { w.TxStore.SpendMultisigOut(&input.PreviousOutPoint, rec.Hash, uint32(i)) } } } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } isStakeType := class == txscript.StakeSubmissionTy || class == txscript.StakeSubChangeTy || class == txscript.StakeGenTy || class == txscript.StakeRevocationTy if isStakeType { class, err = txscript.GetStakeOutSubclass(output.PkScript) if err != nil { log.Errorf("Unknown stake output subclass encountered") continue } } switch { case class == txscript.PubKeyHashTy: for _, addr := range addrs { ma, err := w.Manager.Address(addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. err = w.TxStore.AddCredit(rec, block, uint32(i), ma.Internal()) if err != nil { return err } err = w.Manager.MarkUsed(addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } // Handle P2SH addresses that are multisignature scripts // with keys that we own. case class == txscript.ScriptHashTy: var expandedScript []byte for _, addr := range addrs { var err error expandedScript, err = w.TxStore.GetTxScript(addr.ScriptAddress()) if err != nil { return err } // TODO make this work, the type conversion is broken cj //scrAddr, err := w.Manager.Address(addr) //if err == nil { // addrTyped := scrAddr.(*waddrmgr.ManagedScriptAddress) // retrievedScript, err := addrTyped.Script() // if err == nil { // expandedScript = retrievedScript // } //} } // We don't have the script for this hash, skip. if expandedScript == nil { continue } // Otherwise, extract the actual addresses and // see if any belong to us. expClass, multisigAddrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, expandedScript, w.chainParams) if err != nil { return err } // Skip non-multisig scripts. if expClass != txscript.MultiSigTy { continue } for _, maddr := range multisigAddrs { _, err := w.Manager.Address(maddr) // An address we own; handle accordingly. if err == nil { errStore := w.TxStore.AddMultisigOut(rec, block, uint32(i)) if errStore != nil { // This will throw if there are multiple private keys // for this multisignature output owned by the wallet, // so it's routed to debug. log.Debugf("unable to add multisignature output: %v", errStore.Error()) } } } } } // TODO: Notify connected clients of the added transaction. bs, err := w.chainSvr.BlockStamp() if err == nil { w.notifyBalances(bs.Height, wtxmgr.BFBalanceSpendable) } return nil }