// TicketsWithAddress returns a slice of ticket hashes that are currently live // corresponding to the given address. // // This function is safe for concurrent access. func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Hash, error) { b.chainLock.RLock() sn := b.bestNode.stakeNode b.chainLock.RUnlock() tickets := sn.LiveTickets() var ticketsWithAddr []chainhash.Hash err := b.db.View(func(dbTx database.Tx) error { var err error for _, hash := range tickets { utxo, err := dbFetchUtxoEntry(dbTx, &hash) if err != nil { return err } _, addrs, _, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, utxo.PkScriptByIndex(0), b.chainParams) if addrs[0].EncodeAddress() == address.EncodeAddress() { ticketsWithAddr = append(ticketsWithAddr, hash) } } return err }) if err != nil { return nil, err } return ticketsWithAddr, nil }
// GetLiveTicketsInBucketData creates a map indicating the ticket hash and the // owner's address for each bucket. Used for an RPC call. func (tmdb *TicketDB) GetLiveTicketsInBucketData( bucket uint8) (map[chainhash.Hash]dcrutil.Address, error) { tmdb.mtx.Lock() defer tmdb.mtx.Unlock() ltbd := make(map[chainhash.Hash]dcrutil.Address) tickets := tmdb.maps.ticketMap[bucket] for _, ticket := range tickets { // Load the ticket from the database and find the address that it's // going to. txReply, err := tmdb.database.FetchTxBySha(&ticket.SStxHash) if err != nil { return nil, err } _, addr, _, err := txscript.ExtractPkScriptAddrs(txReply[0].Tx.TxOut[0].Version, txReply[0].Tx.TxOut[0].PkScript, tmdb.chainParams) if err != nil { return nil, err } ltbd[ticket.SStxHash] = addr[0] } return ltbd, nil }
// This example demonstrates extracting information from a standard public key // script. func ExampleExtractPkScriptAddrs() { // Start with a standard pay-to-pubkey-hash script. scriptHex := "76a914128004ff2fcaf13b2b91eb654b1dc2b674f7ec6188ac" script, err := hex.DecodeString(scriptHex) if err != nil { fmt.Println(err) return } // Extract and print details from the script. scriptClass, addresses, reqSigs, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, script, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } fmt.Println("Script Class:", scriptClass) fmt.Println("Addresses:", addresses) fmt.Println("Required Signatures:", reqSigs) // Output: // Script Class: pubkeyhash // Addresses: [DsSej1qR3Fyc8kV176DCh9n9cY9nqf9Quxk] // Required Signatures: 1 }
func lookupInputAccount(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) // TODO: Debits should record which account(s?) they // debit from so this doesn't need to be looked up. prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint prev, err := w.TxStore.TxDetails(txmgrNs, &prevOP.Hash) if err != nil { log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err) return 0 } if prev == nil { log.Errorf("Missing previous transaction %v", prevOP.Hash) return 0 } prevOut := prev.MsgTx.TxOut[prevOP.Index] _, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.Version, prevOut.PkScript, w.chainParams) var inputAcct uint32 if err == nil && len(addrs) > 0 { inputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err != nil { log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err) inputAcct = 0 } return inputAcct }
// GetLiveTicketsForAddress gets all currently active tickets for a given // address. func (tmdb *TicketDB) GetLiveTicketsForAddress( address dcrutil.Address) ([]chainhash.Hash, error) { tmdb.mtx.Lock() defer tmdb.mtx.Unlock() var ltfa []chainhash.Hash for i := 0; i < BucketsSize; i++ { for _, ticket := range tmdb.maps.ticketMap[i] { // Load the ticket from the database and find the address that it's // going to. txReply, err := tmdb.database.FetchTxBySha(&ticket.SStxHash) if err != nil { return nil, err } _, addr, _, err := txscript.ExtractPkScriptAddrs(txReply[0].Tx.TxOut[0].Version, txReply[0].Tx.TxOut[0].PkScript, tmdb.chainParams) if err != nil { return nil, err } // Compare the HASH160 result and see if it's equal. if bytes.Equal(addr[0].ScriptAddress(), address.ScriptAddress()) { ltfa = append(ltfa, ticket.SStxHash) } } } return ltfa, nil }
// signMultiSigUTXO signs the P2SH UTXO with the given index by constructing a // script containing all given signatures plus the redeem (multi-sig) script. The // redeem script is obtained by looking up the address of the given P2SH pkScript // on the address manager. // The order of the signatures must match that of the public keys in the multi-sig // script as OP_CHECKMULTISIG expects that. // This function must be called with the manager unlocked. func signMultiSigUTXO(mgr *waddrmgr.Manager, tx *wire.MsgTx, idx int, pkScript []byte, sigs []RawSig) error { class, addresses, _, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, pkScript, mgr.ChainParams()) if err != nil { return newError(ErrTxSigning, "unparseable pkScript", err) } if class != txscript.ScriptHashTy { return newError(ErrTxSigning, fmt.Sprintf("pkScript is not P2SH: %s", class), nil) } redeemScript, err := getRedeemScript(mgr, addresses[0].(*dcrutil.AddressScriptHash)) if err != nil { return newError(ErrTxSigning, "unable to retrieve redeem script", err) } class, _, nRequired, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, redeemScript, mgr.ChainParams()) if err != nil { return newError(ErrTxSigning, "unparseable redeem script", err) } if class != txscript.MultiSigTy { return newError(ErrTxSigning, fmt.Sprintf("redeem script is not multi-sig: %v", class), nil) } if len(sigs) < nRequired { errStr := fmt.Sprintf("not enough signatures; need %d but got only %d", nRequired, len(sigs)) return newError(ErrTxSigning, errStr, nil) } // Construct the unlocking script. // Start with an OP_0 because of the bug in bitcoind, then add nRequired signatures. unlockingScript := txscript.NewScriptBuilder().AddOp(txscript.OP_FALSE) for _, sig := range sigs[:nRequired] { unlockingScript.AddData(sig) } // Combine the redeem script and the unlocking script to get the actual signature script. sigScript := unlockingScript.AddData(redeemScript) script, err := sigScript.Script() if err != nil { return newError(ErrTxSigning, "error building sigscript", err) } tx.TxIn[idx].SignatureScript = script if err := validateSigScript(tx, idx, pkScript); err != nil { return err } return nil }
// GetSSGenStakeOutputInfo takes an SSGen tx as input and scans through its // outputs, returning the amount of the output and the PKH or SH that it was // sent to. func GetSSGenStakeOutputInfo(tx *dcrutil.Tx, params *chaincfg.Params) ([]bool, [][]byte, []int64, error) { msgTx := tx.MsgTx() numOutputsInSSGen := len(msgTx.TxOut) isP2SH := make([]bool, numOutputsInSSGen-2) addresses := make([][]byte, numOutputsInSSGen-2) amounts := make([]int64, numOutputsInSSGen-2) // Cycle through the inputs and generate for idx, out := range msgTx.TxOut { // We only care about the outputs where we get proportional // amounts and the PKHs they were sent to. if (idx > 1) && (idx < numOutputsInSSGen) { // Get the PKH or SH it's going to, and what type of // script it is. class, addr, _, err := txscript.ExtractPkScriptAddrs(out.Version, out.PkScript, params) if err != nil { return nil, nil, nil, err } if class != txscript.StakeGenTy { return nil, nil, nil, fmt.Errorf("ssgen output included non "+ "ssgen tagged output in idx %v", idx) } subClass, err := txscript.GetStakeOutSubclass(out.PkScript) if !(subClass == txscript.PubKeyHashTy || subClass == txscript.ScriptHashTy) { return nil, nil, nil, fmt.Errorf("bad script type") } isP2SH[idx-2] = false if subClass == txscript.ScriptHashTy { isP2SH[idx-2] = true } // Get the amount that was sent. amt := out.Value addresses[idx-2] = addr[0].ScriptAddress() amounts[idx-2] = amt } } return isP2SH, addresses, amounts, nil }
func lookupOutputChain(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails, cred wtxmgr.CreditRecord) (account uint32, internal bool) { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) output := details.MsgTx.TxOut[cred.Index] _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version, output.PkScript, w.chainParams) var ma waddrmgr.ManagedAddress if err == nil && len(addrs) > 0 { ma, err = w.Manager.Address(addrmgrNs, addrs[0]) } if err != nil { log.Errorf("Cannot fetch account for wallet output: %v", err) } else { account = ma.Account() internal = ma.Internal() } return }
// AddTicket adds a ticket transaction to the wallet. func (w *Wallet) AddTicket(ticket *dcrutil.Tx) error { return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { stakemgrNs := tx.ReadWriteBucket(wstakemgrNamespaceKey) // Insert the ticket to be tracked and voted. err := w.StakeMgr.InsertSStx(stakemgrNs, ticket, w.VoteBits) if err != nil { return err } if w.stakePoolEnabled { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) // Pluck the ticketaddress to indentify the stakepool user. pkVersion := ticket.MsgTx().TxOut[0].Version pkScript := ticket.MsgTx().TxOut[0].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkVersion, pkScript, w.ChainParams()) if err != nil { return err } ticketHash := ticket.MsgTx().TxSha() chainClient, err := w.requireChainClient() if err != nil { return err } rawTx, err := chainClient.GetRawTransactionVerbose(&ticketHash) if err != nil { return err } // Update the pool ticket stake. This will include removing it from the // invalid slice and adding a ImmatureOrLive ticket to the valid ones. err = w.updateStakePoolInvalidTicket(stakemgrNs, addrmgrNs, addrs[0], &ticketHash, rawTx.BlockHeight) if err != nil { return err } } return nil }) }
// indexUnconfirmedAddresses modifies the unconfirmed (memory-only) address // index to include mappings for the addresses encoded by the passed public key // script to the transaction. // // This function is safe for concurrent access. func (idx *AddrIndex) indexUnconfirmedAddresses(scriptVersion uint16, pkScript []byte, tx *dcrutil.Tx, isSStx bool) { // The error is ignored here since the only reason it can fail is if the // script fails to parse and it was already validated before being // admitted to the mempool. class, addresses, _, _ := txscript.ExtractPkScriptAddrs(scriptVersion, pkScript, idx.chainParams) if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(pkScript, idx.chainParams) if err != nil { // Fail if this fails to decode. It should. return } addresses = append(addresses, addr) } for _, addr := range addresses { // Ignore unsupported address types. addrKey, err := addrToKey(addr, idx.chainParams) if err != nil { continue } // Add a mapping from the address to the transaction. idx.unconfirmedLock.Lock() addrIndexEntry := idx.txnsByAddr[addrKey] if addrIndexEntry == nil { addrIndexEntry = make(map[chainhash.Hash]*dcrutil.Tx) idx.txnsByAddr[addrKey] = addrIndexEntry } addrIndexEntry[*tx.Sha()] = tx // Add a mapping from the transaction to the address. addrsByTxEntry := idx.addrsByTx[*tx.Sha()] if addrsByTxEntry == nil { addrsByTxEntry = make(map[[addrKeySize]byte]struct{}) idx.addrsByTx[*tx.Sha()] = addrsByTxEntry } addrsByTxEntry[addrKey] = struct{}{} idx.unconfirmedLock.Unlock() } }
// indexPkScript extracts all standard addresses from the passed public key // script and maps each of them to the associated transaction using the passed // map. func (idx *AddrIndex) indexPkScript(data writeIndexData, scriptVersion uint16, pkScript []byte, txIdx int, isSStx bool) { // Nothing to index if the script is non-standard or otherwise doesn't // contain any addresses. class, addrs, _, err := txscript.ExtractPkScriptAddrs(scriptVersion, pkScript, idx.chainParams) if err != nil { return } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(pkScript, idx.chainParams) if err != nil { return } addrs = append(addrs, addr) } if len(addrs) == 0 { return } for _, addr := range addrs { addrKey, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } // Avoid inserting the transaction more than once. Since the // transactions are indexed serially any duplicates will be // indexed in a row, so checking the most recent entry for the // address is enough to detect duplicates. indexedTxns := data[addrKey] numTxns := len(indexedTxns) if numTxns > 0 && indexedTxns[numTxns-1] == txIdx { continue } indexedTxns = append(indexedTxns, txIdx) data[addrKey] = indexedTxns } }
// TxSSRtxStakeOutputInfo takes an SSRtx tx as input and scans through its // outputs, returning the amount of the output and the pkh that it was sent to. func TxSSRtxStakeOutputInfo(tx *wire.MsgTx, params *chaincfg.Params) ([]bool, [][]byte, []int64, error) { numOutputsInSSRtx := len(tx.TxOut) isP2SH := make([]bool, numOutputsInSSRtx) addresses := make([][]byte, numOutputsInSSRtx) amounts := make([]int64, numOutputsInSSRtx) // Cycle through the inputs and generate for idx, out := range tx.TxOut { // Get the PKH or SH it's going to, and what type of // script it is. class, addr, _, err := txscript.ExtractPkScriptAddrs(out.Version, out.PkScript, params) if err != nil { return nil, nil, nil, err } if class != txscript.StakeRevocationTy { return nil, nil, nil, fmt.Errorf("ssrtx output included non "+ "ssrtx tagged output in idx %v", idx) } subClass, err := txscript.GetStakeOutSubclass(out.PkScript) if !(subClass == txscript.PubKeyHashTy || subClass == txscript.ScriptHashTy) { return nil, nil, nil, fmt.Errorf("bad script type") } isP2SH[idx] = false if subClass == txscript.ScriptHashTy { isP2SH[idx] = true } // Get the amount that was sent. amt := out.Value addresses[idx] = addr[0].ScriptAddress() amounts[idx] = amt } return isP2SH, addresses, amounts, nil }
func totalBalances(dbtx walletdb.ReadTx, w *Wallet, m map[uint32]dcrutil.Amount) error { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) unspent, err := w.TxStore.UnspentOutputs(dbtx.ReadBucket(wtxmgrNamespaceKey)) if err != nil { return err } for i := range unspent { output := unspent[i] var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err == nil { _, ok := m[outputAcct] if ok { m[outputAcct] += output.Amount } } } return nil }
// groupCreditsByAddr converts a slice of credits to a map from the string // representation of an encoded address to the unspent outputs associated with // that address. func groupCreditsByAddr(credits []*wtxmgr.Credit, chainParams *chaincfg.Params) ( map[string][]wtxmgr.Credit, error) { addrMap := make(map[string][]wtxmgr.Credit) for _, c := range credits { _, addrs, _, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, c.PkScript, chainParams) if err != nil { return nil, newError(ErrInputSelection, "failed to obtain input address", err) } // As our credits are all P2SH we should never have more than one // address per credit, so let's error out if that assumption is // violated. if len(addrs) != 1 { return nil, newError(ErrInputSelection, "input doesn't have exactly one address", nil) } encAddr := addrs[0].EncodeAddress() if v, ok := addrMap[encAddr]; ok { addrMap[encAddr] = append(v, *c) } else { addrMap[encAddr] = []wtxmgr.Credit{*c} } } return addrMap, nil }
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 }
// convertToAddrIndex indexes all data pushes greater than 8 bytes within the // passed SPK and returns a TxAddrIndex with the given data. Our "address" // index is actually a hash160 index, where in the ideal case the data push // is either the hash160 of a publicKey (P2PKH) or a Script (P2SH). func convertToAddrIndex(scrVersion uint16, scr []byte, height int64, locInBlock *wire.TxLoc, txType stake.TxType) ([]*database.TxAddrIndex, error) { var tais []*database.TxAddrIndex if scr == nil || locInBlock == nil { return nil, fmt.Errorf("passed nil pointer") } var indexKey [ripemd160.Size]byte // Get the script classes and extract the PKH if applicable. // If it's multisig, unknown, etc, just hash the script itself. class, addrs, _, err := txscript.ExtractPkScriptAddrs(scrVersion, scr, activeNetParams.Params) if err != nil { return nil, fmt.Errorf("script conversion error: %v", err.Error()) } knownType := false for _, addr := range addrs { switch { case class == txscript.PubKeyTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubkeyAltTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubKeyHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.PubkeyHashAltTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubmissionTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeGenTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeRevocationTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubChangeTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.MultiSigTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.ScriptHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) } tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) knownType = true } // This is a commitment for a future vote or // revocation. Extract the address data from // it and store it in the addrIndex. if txType == stake.TxTypeSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(scr, activeNetParams.Params) if err != nil { return nil, fmt.Errorf("ticket commit pkscr conversion error: %v", err.Error()) } copy(indexKey[:], addr.ScriptAddress()[:]) tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) } else if !knownType { copy(indexKey[:], dcrutil.Hash160(scr)) tai := &database.TxAddrIndex{ Hash160: indexKey, Height: uint32(height), TxOffset: uint32(locInBlock.TxStart), TxLen: uint32(locInBlock.TxLen), } tais = append(tais, tai) } return tais, nil }
// TestExtractPkScriptAddrs ensures that extracting the type, addresses, and // number of required signatures from PkScripts works as intended. func TestExtractPkScriptAddrs(t *testing.T) { t.Parallel() tests := []struct { name string script []byte addrs []dcrutil.Address reqSigs int class txscript.ScriptClass }{ { name: "standard p2pk with compressed pubkey (0x02)", script: decodeHex("2102192d74d0cb94344c9569c2e7790157" + "3d8d7903c3ebec3a957724895dca52c6b4ac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("02192d74d0cb94344" + "c9569c2e77901573d8d7903c3ebec3a95772" + "4895dca52c6b4")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with uncompressed pubkey (0x04)", script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + "f656b412a3ac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("0411db93e1dcdb8a0" + "16b49840f8c53bc1eb68a382e97b1482ecad" + "7b148a6909a5cb2e0eaddfb84ccf9744464f" + "82e160bfa9b8b64f9d4c03f999b8643f656b" + "412a3")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with hybrid pubkey (0x06)", script: decodeHex("4106192d74d0cb94344c9569c2e7790157" + "3d8d7903c3ebec3a957724895dca52c6b40d45264838" + "c0bd96852662ce6a847b197376830160c6d2eb5e6a4c" + "44d33f453eac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("06192d74d0cb94344" + "c9569c2e77901573d8d7903c3ebec3a95772" + "4895dca52c6b40d45264838c0bd96852662c" + "e6a847b197376830160c6d2eb5e6a4c44d33" + "f453e")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with compressed pubkey (0x03)", script: decodeHex("2103b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e65ac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("03b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e65")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "2nd standard p2pk with uncompressed pubkey (0x04)", script: decodeHex("4104b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + "1e0908ef7bac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("04b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e6537a576782eba668a7ef8bd3" + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + "8ef7b")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with hybrid pubkey (0x07)", script: decodeHex("4107b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + "1e0908ef7bac"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("07b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e6537a576782eba668a7ef8bd3" + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + "8ef7b")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pkh", script: decodeHex("76a914ad06dd6ddee55cbca9a9e3713bd7" + "587509a3056488ac"), addrs: []dcrutil.Address{ newAddressPubKeyHash(decodeHex("ad06dd6ddee55" + "cbca9a9e3713bd7587509a30564")), }, reqSigs: 1, class: txscript.PubKeyHashTy, }, { name: "standard p2sh", script: decodeHex("a91463bcc565f9e68ee0189dd5cc67f1b0" + "e5f02f45cb87"), addrs: []dcrutil.Address{ newAddressScriptHash(decodeHex("63bcc565f9e68" + "ee0189dd5cc67f1b0e5f02f45cb")), }, reqSigs: 1, class: txscript.ScriptHashTy, }, // from real tx 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1, vout 0 { name: "standard 1 of 2 multisig", script: decodeHex("514104cc71eb30d653c0c3163990c47b97" + "6f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473" + "e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11" + "fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381" + "354d80e550078cb532a34bfa2fcfdeb7d76519aecc62" + "770f5b0e4ef8551946d8a540911abe3e7854a26f39f5" + "8b25c15342af52ae"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("04cc71eb30d653c0c" + "3163990c47b976f3fb3f37cccdcbedb169a1" + "dfef58bbfbfaff7d8a473e7e2e6d317b87ba" + "fe8bde97e3cf8f065dec022b51d11fcdd0d3" + "48ac4")), newAddressPubKey(decodeHex("0461cbdcc5409fb4b" + "4d42b51d33381354d80e550078cb532a34bf" + "a2fcfdeb7d76519aecc62770f5b0e4ef8551" + "946d8a540911abe3e7854a26f39f58b25c15" + "342af")), }, reqSigs: 1, class: txscript.MultiSigTy, }, // from real tx d646f82bd5fbdb94a36872ce460f97662b80c3050ad3209bef9d1e398ea277ab, vin 1 { name: "standard 2 of 3 multisig", script: decodeHex("524104cb9c3c222c5f7a7d3b9bd152f363" + "a0b6d54c9eb312c4d4f9af1e8551b6c421a6a4ab0e29" + "105f24de20ff463c1c91fcf3bf662cdde4783d4799f7" + "87cb7c08869b4104ccc588420deeebea22a7e900cc8b" + "68620d2212c374604e3487ca08f1ff3ae12bdc639514" + "d0ec8612a2d3c519f084d9a00cbbe3b53d071e9b09e7" + "1e610b036aa24104ab47ad1939edcb3db65f7fedea62" + "bbf781c5410d3f22a7a3a56ffefb2238af8627363bdf" + "2ed97c1f89784a1aecdb43384f11d2acc64443c7fc29" + "9cef0400421a53ae"), addrs: []dcrutil.Address{ newAddressPubKey(decodeHex("04cb9c3c222c5f7a7" + "d3b9bd152f363a0b6d54c9eb312c4d4f9af1" + "e8551b6c421a6a4ab0e29105f24de20ff463" + "c1c91fcf3bf662cdde4783d4799f787cb7c0" + "8869b")), newAddressPubKey(decodeHex("04ccc588420deeebe" + "a22a7e900cc8b68620d2212c374604e3487c" + "a08f1ff3ae12bdc639514d0ec8612a2d3c51" + "9f084d9a00cbbe3b53d071e9b09e71e610b0" + "36aa2")), newAddressPubKey(decodeHex("04ab47ad1939edcb3" + "db65f7fedea62bbf781c5410d3f22a7a3a56" + "ffefb2238af8627363bdf2ed97c1f89784a1" + "aecdb43384f11d2acc64443c7fc299cef040" + "0421a")), }, reqSigs: 2, class: txscript.MultiSigTy, }, // The below are nonstandard script due to things such as // invalid pubkeys, failure to parse, and not being of a // standard form. { name: "p2pk with uncompressed pk missing OP_CHECKSIG", script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + "f656b412a3"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, { name: "valid signature from a sigscript - no addresses", script: decodeHex("47304402204e45e16932b8af514961a1d3" + "a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220" + "181522ec8eca07de4860a4acdd12909d831cc56cbbac" + "4622082221a8768d1d0901"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, // Note the technically the pubkey is the second item on the // stack, but since the address extraction intentionally only // works with standard PkScripts, this should not return any // addresses. { name: "valid sigscript to reedeem p2pk - no addresses", script: decodeHex("493046022100ddc69738bf2336318e4e04" + "1a5a77f305da87428ab1606f023260017854350ddc02" + "2100817af09d2eec36862d16009852b7e3a0f6dd7659" + "8290b7834e1453660367e07a014104cd4240c198e125" + "23b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11c" + "25b1dff9a519675d198804ba9962d3eca2d5937d58e5" + "a75a71042d40388a4d307f887d"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, // from real tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 0 // invalid public keys { name: "1 of 3 multisig with invalid pubkeys", script: decodeHex("51411c2200007353455857696b696c6561" + "6b73204361626c6567617465204261636b75700a0a63" + "61626c65676174652d3230313031323034313831312e" + "377a0a0a446f41776e6c6f61642074686520666f6c6c" + "6f77696e67207472616e73616374696f6e7320776974" + "68205361746f736869204e616b616d6f746f27732064" + "6f776e6c6f61416420746f6f6c2077686963680a6361" + "6e20626520666f756e6420696e207472616e73616374" + "696f6e20366335336364393837313139656637393764" + "35616463636453ae"), addrs: []dcrutil.Address{}, reqSigs: 1, class: txscript.MultiSigTy, }, // from real tx: 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 44 // invalid public keys { name: "1 of 3 multisig with invalid pubkeys 2", script: decodeHex("5141346333656332353963373464616365" + "36666430383862343463656638630a63363662633139" + "39366338623934613338313162333635363138666531" + "65396231623541366361636365393933613339383861" + "34363966636336643664616266640a32363633636661" + "39636634633033633630396335393363336539316665" + "64653730323921313233646434326432353633396433" + "38613663663530616234636434340a00000053ae"), addrs: []dcrutil.Address{}, reqSigs: 1, class: txscript.MultiSigTy, }, { name: "empty script", script: []byte{}, addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, { name: "script that does not parse", script: []byte{txscript.OP_DATA_45}, addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, } t.Logf("Running %d tests.", len(tests)) for i, test := range tests { class, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, test.script, &chaincfg.MainNetParams) if err != nil { } if !reflect.DeepEqual(addrs, test.addrs) { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "addresses\ngot %v\nwant %v", i, test.name, addrs, test.addrs) continue } if reqSigs != test.reqSigs { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "number of required signatures - got %d, "+ "want %d", i, test.name, reqSigs, test.reqSigs) continue } if class != test.class { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "script type - got %s, want %s", i, test.name, class, test.class) continue } } }
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 }
// testAddrIndexOperations ensures that all normal operations concerning // the optional address index function correctly. func testAddrIndexOperations(t *testing.T, db database.Db, newestBlock *dcrutil.Block, newestSha *chainhash.Hash, newestBlockIdx int64) { // Metadata about the current addr index state should be unset. sha, height, err := db.FetchAddrIndexTip() if err != database.ErrAddrIndexDoesNotExist { t.Fatalf("Address index metadata shouldn't be in db, hasn't been built up yet.") } var zeroHash chainhash.Hash if !sha.IsEqual(&zeroHash) { t.Fatalf("AddrIndexTip wrong hash got: %s, want %s", sha, &zeroHash) } if height != -1 { t.Fatalf("Addrindex not built up, yet a block index tip has been set to: %d.", height) } // Test enforcement of constraints for "limit" and "skip" var fakeAddr dcrutil.Address _, err = db.FetchTxsForAddr(fakeAddr, -1, 0) if err == nil { t.Fatalf("Negative value for skip passed, should return an error") } _, err = db.FetchTxsForAddr(fakeAddr, 0, -1) if err == nil { t.Fatalf("Negative value for limit passed, should return an error") } // Simple test to index outputs(s) of the first tx. testIndex := make(database.BlockAddrIndex, database.AddrIndexKeySize) testTx, err := newestBlock.Tx(0) if err != nil { t.Fatalf("Block has no transactions, unable to test addr "+ "indexing, err %v", err) } // Extract the dest addr from the tx. _, testAddrs, _, err := txscript.ExtractPkScriptAddrs(testTx.MsgTx().TxOut[0].Version, testTx.MsgTx().TxOut[0].PkScript, &chaincfg.MainNetParams) if err != nil { t.Fatalf("Unable to decode tx output, err %v", err) } // Extract the hash160 from the output script. var hash160Bytes [ripemd160.Size]byte testHash160 := testAddrs[0].(*dcrutil.AddressScriptHash).Hash160() copy(hash160Bytes[:], testHash160[:]) // Create a fake index. blktxLoc, _, _ := newestBlock.TxLoc() testIndex = []*database.TxAddrIndex{ &database.TxAddrIndex{ Hash160: hash160Bytes, Height: uint32(newestBlockIdx), TxOffset: uint32(blktxLoc[0].TxStart), TxLen: uint32(blktxLoc[0].TxLen), }, } // Insert our test addr index into the DB. err = db.UpdateAddrIndexForBlock(newestSha, newestBlockIdx, testIndex) if err != nil { t.Fatalf("UpdateAddrIndexForBlock: failed to index"+ " addrs for block #%d (%s) "+ "err %v", newestBlockIdx, newestSha, err) } // Chain Tip of address should've been updated. assertAddrIndexTipIsUpdated(db, t, newestSha, newestBlockIdx) // Check index retrieval. txReplies, err := db.FetchTxsForAddr(testAddrs[0], 0, 1000) if err != nil { t.Fatalf("FetchTxsForAddr failed to correctly fetch txs for an "+ "address, err %v", err) } // Should have one reply. if len(txReplies) != 1 { t.Fatalf("Failed to properly index tx by address.") } // Our test tx and indexed tx should have the same sha. indexedTx := txReplies[0] if !bytes.Equal(indexedTx.Sha.Bytes(), testTx.Sha().Bytes()) { t.Fatalf("Failed to fetch proper indexed tx. Expected sha %v, "+ "fetched %v", testTx.Sha(), indexedTx.Sha) } // Shut down DB. db.Sync() db.Close() // Re-Open, tip still should be updated to current height and sha. db, err = database.OpenDB("leveldb", "tstdbopmode") if err != nil { t.Fatalf("Unable to re-open created db, err %v", err) } assertAddrIndexTipIsUpdated(db, t, newestSha, newestBlockIdx) // Delete the entire index. err = db.PurgeAddrIndex() if err != nil { t.Fatalf("Couldn't delete address index, err %v", err) } // Former index should no longer exist. txReplies, err = db.FetchTxsForAddr(testAddrs[0], 0, 1000) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReplies) != 0 { t.Fatalf("Address index was not successfully deleted. "+ "Should have 0 tx's indexed, %v were returned.", len(txReplies)) } // Tip should be blanked out. if _, _, err := db.FetchAddrIndexTip(); err != database.ErrAddrIndexDoesNotExist { t.Fatalf("Address index was not fully deleted.") } }
// convertToAddrIndex indexes all data pushes greater than 8 bytes within the // passed SPK and returns a TxAddrIndex with the given data. Our "address" // index is actually a hash160 index, where in the ideal case the data push // is either the hash160 of a publicKey (P2PKH) or a Script (P2SH). func convertToAddrIndex(scrVersion uint16, scr []byte, height int64, locInBlock *wire.TxLoc) ([]*database.TxAddrIndex, error) { var tais []*database.TxAddrIndex if scr == nil || locInBlock == nil { return nil, fmt.Errorf("passed nil pointer") } var indexKey [ripemd160.Size]byte // Get the script classes and extract the PKH if applicable. // If it's multisig, unknown, etc, just hash the script itself. class, addrs, _, err := txscript.ExtractPkScriptAddrs(scrVersion, scr, activeNetParams.Params) if err != nil { return nil, fmt.Errorf("script conversion error: %v", err.Error()) } knownType := false for _, addr := range addrs { switch { case class == txscript.PubKeyTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubkeyAltTy: copy(indexKey[:], addr.Hash160()[:]) case class == txscript.PubKeyHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.PubkeyHashAltTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubmissionTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeGenTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeRevocationTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.StakeSubChangeTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.MultiSigTy: copy(indexKey[:], addr.ScriptAddress()[:]) case class == txscript.ScriptHashTy: copy(indexKey[:], addr.ScriptAddress()[:]) } tai := &database.TxAddrIndex{ indexKey, uint32(height), uint32(locInBlock.TxStart), uint32(locInBlock.TxLen), } tais = append(tais, tai) knownType = true } if !knownType { copy(indexKey[:], dcrutil.Hash160(scr)) tai := &database.TxAddrIndex{ indexKey, uint32(height), uint32(locInBlock.TxStart), uint32(locInBlock.TxLen), } tais = append(tais, tai) } return tais, nil }
// ConnectBlock is invoked by the index manager when a new block has been // connected to the main chain. This indexer adds a key for each address // the transactions in the block involve. // // This is part of the Indexer interface. func (idx *ExistsAddrIndex) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { regularTxTreeValid := dcrutil.IsFlagSet16(block.MsgBlock().Header.VoteBits, dcrutil.BlockValid) var parentTxs []*dcrutil.Tx if regularTxTreeValid && block.Height() > 1 { parentTxs = parent.Transactions() } blockTxns := block.STransactions() allTxns := append(parentTxs, blockTxns...) usedAddrs := make(map[[addrKeySize]byte]struct{}) for _, tx := range allTxns { msgTx := tx.MsgTx() isSStx, _ := stake.IsSStx(msgTx) for _, txIn := range msgTx.TxIn { if txscript.IsMultisigSigScript(txIn.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( txIn.SignatureScript) if err != nil { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { continue } usedAddrs[k] = struct{}{} } } } for _, txOut := range tx.MsgTx().TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs( txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(txOut.PkScript, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } addrs = append(addrs, addr) } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } usedAddrs[k] = struct{}{} } } } // Write all the newly used addresses to the database, // skipping any keys that already exist. Write any // addresses we see in mempool at this time, too, // then remove them from the unconfirmed map drop // dropping the old map and reassigning a new map. idx.unconfirmedLock.Lock() for k := range idx.mpExistsAddr { usedAddrs[k] = struct{}{} } idx.mpExistsAddr = make(map[[addrKeySize]byte]struct{}) idx.unconfirmedLock.Unlock() meta := dbTx.Metadata() existsAddrIndex := meta.Bucket(existsAddrIndexKey) newUsedAddrs := make(map[[addrKeySize]byte]struct{}) for k := range usedAddrs { if !idx.existsAddress(existsAddrIndex, k) { newUsedAddrs[k] = struct{}{} } } for k := range newUsedAddrs { err := dbPutExistsAddr(existsAddrIndex, k) if err != nil { return err } } return nil }
// addUnconfirmedTx adds all addresses related to the transaction to the // unconfirmed (memory-only) exists address index. func (idx *ExistsAddrIndex) addUnconfirmedTx(tx *wire.MsgTx) { isSStx, _ := stake.IsSStx(tx) for _, txIn := range tx.TxIn { if txscript.IsMultisigSigScript(txIn.SignatureScript) { rs, err := txscript.MultisigRedeemScriptFromScriptSig( txIn.SignatureScript) if err != nil { continue } class, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, rs, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if class != txscript.MultiSigTy { // This should never happen, but be paranoid. continue } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { continue } if _, exists := idx.mpExistsAddr[k]; !exists { idx.mpExistsAddr[k] = struct{}{} } } } } for _, txOut := range tx.TxOut { class, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.Version, txOut.PkScript, idx.chainParams) if err != nil { // Non-standard outputs are skipped. continue } if isSStx && class == txscript.NullDataTy { addr, err := stake.AddrFromSStxPkScrCommitment(txOut.PkScript, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } addrs = append(addrs, addr) } for _, addr := range addrs { k, err := addrToKey(addr, idx.chainParams) if err != nil { // Ignore unsupported address types. continue } if _, exists := idx.mpExistsAddr[k]; !exists { idx.mpExistsAddr[k] = struct{}{} } } } }
// UnspentOutputs fetches all unspent outputs from the wallet that match rules // described in the passed policy. func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) { var outputResults []*TransactionOutput err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) _, tipHeight := w.TxStore.MainChainTip(txmgrNs) // TODO: actually stream outputs from the db instead of fetching // all of them at once. outputs, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } for _, output := range outputs { // Ignore outputs that haven't reached the required // number of confirmations. if !policy.meetsRequiredConfs(output.Height, tipHeight) { continue } // Ignore outputs that are not controlled by the account. _, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, output.PkScript, w.chainParams) if err != nil || len(addrs) == 0 { // Cannot determine which account this belongs // to without a valid address. TODO: Fix this // by saving outputs per account, or accounts // per output. continue } outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err != nil { return err } if outputAcct != policy.Account { continue } // Stakebase isn't exposed by wtxmgr so those will be // OutputKindNormal for now. outputSource := OutputKindNormal if output.FromCoinBase { outputSource = OutputKindCoinbase } result := &TransactionOutput{ OutPoint: output.OutPoint, Output: wire.TxOut{ Value: int64(output.Amount), // TODO: version is bogus but there is // only version 0 at time of writing. Version: txscript.DefaultScriptVersion, PkScript: output.PkScript, }, OutputKind: outputSource, ContainingBlock: BlockIdentity(output.Block), ReceiveTime: output.Received, } outputResults = append(outputResults, result) } return nil }) return outputResults, err }
// CoinbasePaysTax checks to see if a given block's coinbase correctly pays // tax to the developer organization. func CoinbasePaysTax(tx *dcrutil.Tx, height uint32, voters uint16, params *chaincfg.Params) error { // Taxes only apply from block 2 onwards. if height <= 1 { return nil } // Tax is disabled. if params.BlockTaxProportion == 0 { return nil } if len(tx.MsgTx().TxOut) == 0 { errStr := fmt.Sprintf("invalid coinbase (no outputs)") return ruleError(ErrNoTxOutputs, errStr) } // Coinbase output 0 must be the subsidy to the dev organization. taxPkVersion := tx.MsgTx().TxOut[0].Version taxPkScript := tx.MsgTx().TxOut[0].PkScript class, addrs, _, err := txscript.ExtractPkScriptAddrs(taxPkVersion, taxPkScript, params) // The script can't be a weird class. if !(class == txscript.ScriptHashTy || class == txscript.PubKeyHashTy || class == txscript.PubKeyTy) { errStr := fmt.Sprintf("wrong script class for tax output") return ruleError(ErrNoTax, errStr) } // There should only be one address. if len(addrs) != 1 { errStr := fmt.Sprintf("no or too many addresses in output") return ruleError(ErrNoTax, errStr) } // Decode the organization address. addrOrg, err := dcrutil.DecodeAddress(params.OrganizationAddress, params) if err != nil { return err } if !bytes.Equal(addrs[0].ScriptAddress(), addrOrg.ScriptAddress()) { errStr := fmt.Sprintf("address in output 0 has non matching org "+ "address; got %v (hash160 %x), want %v (hash160 %x)", addrs[0].EncodeAddress(), addrs[0].ScriptAddress(), addrOrg.EncodeAddress(), addrOrg.ScriptAddress()) return ruleError(ErrNoTax, errStr) } // Get the amount of subsidy that should have been paid out to // the organization, then check it. orgSubsidy := CalcBlockTaxSubsidy(int64(height), voters, params) amountFound := tx.MsgTx().TxOut[0].Value if orgSubsidy != amountFound { errStr := fmt.Sprintf("amount in output 0 has non matching org "+ "calculated amount; got %v, want %v", amountFound, orgSubsidy) return ruleError(ErrNoTax, errStr) } return nil }
// BlockOneCoinbasePaysTokens checks to see if the first block coinbase pays // out to the network initial token ledger. func BlockOneCoinbasePaysTokens(tx *dcrutil.Tx, params *chaincfg.Params) error { // If no ledger is specified, just return true. if len(params.BlockOneLedger) == 0 { return nil } if tx.MsgTx().LockTime != 0 { errStr := fmt.Sprintf("block 1 coinbase has invalid locktime") return ruleError(ErrBlockOneTx, errStr) } if tx.MsgTx().Expiry != wire.NoExpiryValue { errStr := fmt.Sprintf("block 1 coinbase has invalid expiry") return ruleError(ErrBlockOneTx, errStr) } if tx.MsgTx().TxIn[0].Sequence != wire.MaxTxInSequenceNum { errStr := fmt.Sprintf("block 1 coinbase not finalized") return ruleError(ErrBlockOneInputs, errStr) } if len(tx.MsgTx().TxOut) == 0 { errStr := fmt.Sprintf("coinbase outputs empty in block 1") return ruleError(ErrBlockOneOutputs, errStr) } ledger := params.BlockOneLedger if len(ledger) != len(tx.MsgTx().TxOut) { errStr := fmt.Sprintf("wrong number of outputs in block 1 coinbase; "+ "got %v, expected %v", len(tx.MsgTx().TxOut), len(ledger)) return ruleError(ErrBlockOneOutputs, errStr) } // Check the addresses and output amounts against those in the ledger. for i, txout := range tx.MsgTx().TxOut { if txout.Version != txscript.DefaultScriptVersion { errStr := fmt.Sprintf("bad block one output version; want %v, got %v", txscript.DefaultScriptVersion, txout.Version) return ruleError(ErrBlockOneOutputs, errStr) } // There should only be one address. _, addrs, _, err := txscript.ExtractPkScriptAddrs(txout.Version, txout.PkScript, params) if len(addrs) != 1 { errStr := fmt.Sprintf("too many addresses in output") return ruleError(ErrBlockOneOutputs, errStr) } addrLedger, err := dcrutil.DecodeAddress(ledger[i].Address, params) if err != nil { return err } if !bytes.Equal(addrs[0].ScriptAddress(), addrLedger.ScriptAddress()) { errStr := fmt.Sprintf("address in output %v has non matching "+ "address; got %v (hash160 %x), want %v (hash160 %x)", i, addrs[0].EncodeAddress(), addrs[0].ScriptAddress(), addrLedger.EncodeAddress(), addrLedger.ScriptAddress()) return ruleError(ErrBlockOneOutputs, errStr) } if txout.Value != ledger[i].Amount { errStr := fmt.Sprintf("address in output %v has non matching "+ "amount; got %v, want %v", i, txout.Value, ledger[i].Amount) return ruleError(ErrBlockOneOutputs, errStr) } } return nil }