decred.org/dcrwallet/v3@v3.1.0/wallet/udb/txunmined.go (about) 1 // Copyright (c) 2013-2016 The btcsuite developers 2 // Copyright (c) 2015-2017 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package udb 7 8 import ( 9 "bytes" 10 11 "decred.org/dcrwallet/v3/errors" 12 "decred.org/dcrwallet/v3/wallet/walletdb" 13 "github.com/decred/dcrd/blockchain/stake/v5" 14 "github.com/decred/dcrd/chaincfg/chainhash" 15 "github.com/decred/dcrd/wire" 16 ) 17 18 // InsertMemPoolTx inserts a memory pool transaction record. It also marks 19 // previous outputs referenced by its inputs as spent. Errors with the 20 // DoubleSpend code if another unmined transaction is a double spend of this 21 // transaction. 22 func (s *Store) InsertMemPoolTx(dbtx walletdb.ReadWriteTx, rec *TxRecord) error { 23 ns := dbtx.ReadWriteBucket(wtxmgrBucketKey) 24 25 _, recVal := latestTxRecord(ns, rec.Hash[:]) 26 if recVal != nil { 27 return errors.E(errors.Exist, "transaction already exists mined") 28 } 29 30 v := existsRawUnmined(ns, rec.Hash[:]) 31 if v != nil { 32 return nil 33 } 34 35 // Check for other unmined transactions which cause this tx to be a double 36 // spend. Unlike mined transactions that double spend an unmined tx, 37 // existing unmined txs are not removed when inserting a double spending 38 // unmined tx. 39 // 40 // An exception is made for this rule for votes and revocations that double 41 // spend an unmined vote that doesn't vote on the tip block. 42 for _, input := range rec.MsgTx.TxIn { 43 prevOut := &input.PreviousOutPoint 44 k := canonicalOutPoint(&prevOut.Hash, prevOut.Index) 45 if v := existsRawUnminedInput(ns, k); v != nil { 46 var spenderHash chainhash.Hash 47 readRawUnminedInputSpenderHash(v, &spenderHash) 48 49 // A switch is used here instead of an if statement so it can be 50 // broken out of to the error below. 51 DoubleSpendVoteCheck: 52 switch rec.TxType { 53 case stake.TxTypeSSGen, stake.TxTypeSSRtx: 54 spenderVal := existsRawUnmined(ns, spenderHash[:]) 55 spenderTxBytes := extractRawUnminedTx(spenderVal) 56 var spenderTx wire.MsgTx 57 err := spenderTx.Deserialize(bytes.NewReader(spenderTxBytes)) 58 if err != nil { 59 return errors.E(errors.IO, err) 60 } 61 if stake.DetermineTxType(&spenderTx) != stake.TxTypeSSGen { 62 break DoubleSpendVoteCheck 63 } 64 votedBlock, _ := stake.SSGenBlockVotedOn(&spenderTx) 65 tipBlock, _ := s.MainChainTip(dbtx) 66 if votedBlock == tipBlock { 67 err := errors.Errorf("vote or revocation %v double spends unmined "+ 68 "vote %v on the tip block", &rec.Hash, &spenderHash) 69 return errors.E(errors.DoubleSpend, err) 70 } 71 72 err = s.RemoveUnconfirmed(ns, &spenderTx, &spenderHash) 73 if err != nil { 74 return err 75 } 76 continue 77 } 78 79 err := errors.Errorf("%v conflicts with %v by double spending %v", &rec.Hash, &spenderHash, prevOut) 80 return errors.E(errors.DoubleSpend, err) 81 } 82 } 83 84 log.Infof("Inserting unconfirmed transaction %v", &rec.Hash) 85 v, err := valueTxRecord(rec) 86 if err != nil { 87 return err 88 } 89 err = putRawUnmined(ns, rec.Hash[:], v) 90 if err != nil { 91 return err 92 } 93 if rec.Unpublished { 94 err = putUnpublished(ns, rec.Hash[:]) 95 if err != nil { 96 return err 97 } 98 } 99 100 txType := stake.DetermineTxType(&rec.MsgTx) 101 102 for i, input := range rec.MsgTx.TxIn { 103 // Skip stakebases for votes. 104 if i == 0 && txType == stake.TxTypeSSGen { 105 continue 106 } 107 prevOut := &input.PreviousOutPoint 108 k := canonicalOutPoint(&prevOut.Hash, prevOut.Index) 109 err = putRawUnminedInput(ns, k, rec.Hash[:]) 110 if err != nil { 111 return err 112 } 113 } 114 115 // If the transaction is a ticket purchase, record it in the ticket 116 // purchases bucket. 117 if txType == stake.TxTypeSStx { 118 tk := rec.Hash[:] 119 tv := existsRawTicketRecord(ns, tk) 120 if tv == nil { 121 tv = valueTicketRecord(-1) 122 err := putRawTicketRecord(ns, tk, tv) 123 if err != nil { 124 return err 125 } 126 } 127 } 128 129 // TODO: increment credit amount for each credit (but those are unknown 130 // here currently). 131 132 return nil 133 } 134 135 // SetPublished modifies the published state of an unmined transaction. 136 func (s *Store) SetPublished(dbtx walletdb.ReadWriteTx, txHash *chainhash.Hash, published bool) error { 137 ns := dbtx.ReadWriteBucket(wtxmgrBucketKey) 138 if published { 139 return deleteUnpublished(ns, txHash[:]) 140 } 141 return putUnpublished(ns, txHash[:]) 142 } 143 144 // removeDoubleSpends checks for any unmined transactions which would introduce 145 // a double spend if tx was added to the store (either as a confirmed or unmined 146 // transaction). Each conflicting transaction and all transactions which spend 147 // it are recursively removed. 148 func (s *Store) removeDoubleSpends(ns walletdb.ReadWriteBucket, rec *TxRecord) error { 149 for _, input := range rec.MsgTx.TxIn { 150 prevOut := &input.PreviousOutPoint 151 prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index) 152 doubleSpendHash := existsRawUnminedInput(ns, prevOutKey) 153 if doubleSpendHash != nil { 154 var doubleSpend TxRecord 155 doubleSpendVal := existsRawUnmined(ns, doubleSpendHash) 156 copy(doubleSpend.Hash[:], doubleSpendHash) // Silly but need an array 157 err := readRawTxRecord(&doubleSpend.Hash, doubleSpendVal, 158 &doubleSpend) 159 if err != nil { 160 return err 161 } 162 163 log.Debugf("Removing double spending transaction %v", 164 doubleSpend.Hash) 165 err = s.RemoveUnconfirmed(ns, &doubleSpend.MsgTx, &doubleSpend.Hash) 166 if err != nil { 167 return err 168 } 169 } 170 } 171 return nil 172 } 173 174 // RemoveUnconfirmed removes an unmined transaction record and all spend chains 175 // deriving from it from the store. This is designed to remove transactions 176 // that would otherwise result in double spend conflicts if left in the store, 177 // and to remove transactions that spend coinbase transactions on reorgs. It 178 // can also be used to remove old tickets that do not meet the network difficulty 179 // and expired transactions. 180 func (s *Store) RemoveUnconfirmed(ns walletdb.ReadWriteBucket, tx *wire.MsgTx, txHash *chainhash.Hash) error { 181 182 stxType := stake.DetermineTxType(tx) 183 184 // For each potential credit for this record, each spender (if any) must 185 // be recursively removed as well. Once the spenders are removed, the 186 // credit is deleted. 187 numOuts := uint32(len(tx.TxOut)) 188 for i := uint32(0); i < numOuts; i++ { 189 k := canonicalOutPoint(txHash, i) 190 spenderHash := existsRawUnminedInput(ns, k) 191 if spenderHash != nil { 192 var spender TxRecord 193 spenderVal := existsRawUnmined(ns, spenderHash) 194 copy(spender.Hash[:], spenderHash) // Silly but need an array 195 err := readRawTxRecord(&spender.Hash, spenderVal, &spender) 196 if err != nil { 197 return err 198 } 199 200 log.Debugf("Transaction %v is part of a removed conflict "+ 201 "chain -- removing as well", spender.Hash) 202 err = s.RemoveUnconfirmed(ns, &spender.MsgTx, &spender.Hash) 203 if err != nil { 204 return err 205 } 206 } 207 err := deleteRawUnminedCredit(ns, k) 208 if err != nil { 209 return err 210 } 211 212 if (stxType == stake.TxTypeSStx) && (i%2 == 1) { 213 // An unconfirmed ticket leaving the store means we need to delete 214 // the respective commitment and its entry from the unspent 215 // commitments index. 216 217 // This assumes that the key to ticket commitment values in the db are 218 // canonicalOutPoints. If this ever changes, this needs to be changed to 219 // use keyTicketCommitment(...). 220 ktc := k 221 vtc := existsRawTicketCommitment(ns, ktc) 222 if vtc != nil { 223 log.Debugf("Removing unconfirmed ticket commitment %s:%d", 224 txHash, i) 225 err = deleteRawTicketCommitment(ns, ktc) 226 if err != nil { 227 return err 228 } 229 err = deleteRawUnspentTicketCommitment(ns, ktc) 230 if err != nil { 231 return err 232 } 233 } 234 } 235 } 236 237 if (stxType == stake.TxTypeSSGen) || (stxType == stake.TxTypeSSRtx) { 238 // An unconfirmed vote/revocation leaving the store means we need to 239 // unmark the commitments of the respective ticket as unminedSpent. 240 err := s.replaceTicketCommitmentUnminedSpent(ns, stxType, tx, false) 241 if err != nil { 242 return err 243 } 244 } 245 246 // If this tx spends any previous credits (either mined or unmined), set 247 // each unspent. Mined transactions are only marked spent by having the 248 // output in the unmined inputs bucket. 249 for _, input := range tx.TxIn { 250 prevOut := &input.PreviousOutPoint 251 k := canonicalOutPoint(&prevOut.Hash, prevOut.Index) 252 err := deleteRawUnminedInput(ns, k) 253 if err != nil { 254 return err 255 } 256 257 // If a multisig output is recorded for this input, mark it unspent. 258 if v := existsMultisigOut(ns, k); v != nil { 259 vcopy := make([]byte, len(v)) 260 copy(vcopy, v) 261 setMultisigOutUnSpent(vcopy) 262 err := putMultisigOutRawValues(ns, k, vcopy) 263 if err != nil { 264 return err 265 } 266 err = putMultisigOutUS(ns, k) 267 if err != nil { 268 return err 269 } 270 } 271 } 272 273 err := deleteUnpublished(ns, txHash[:]) 274 if err != nil { 275 return err 276 } 277 278 return deleteRawUnmined(ns, txHash[:]) 279 } 280 281 // UnminedTxs returns the transaction records for all unmined transactions 282 // which are not known to have been mined in a block. Transactions are 283 // guaranteed to be sorted by their dependency order. 284 func (s *Store) UnminedTxs(dbtx walletdb.ReadTx) ([]*TxRecord, error) { 285 ns := dbtx.ReadBucket(wtxmgrBucketKey) 286 recSet, err := s.unminedTxRecords(ns) 287 if err != nil { 288 return nil, err 289 } 290 291 recs := dependencySort(recSet) 292 return recs, nil 293 } 294 295 func (s *Store) unminedTxRecords(ns walletdb.ReadBucket) (map[chainhash.Hash]*TxRecord, error) { 296 unmined := make(map[chainhash.Hash]*TxRecord) 297 err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error { 298 var txHash chainhash.Hash 299 err := readRawUnminedHash(k, &txHash) 300 if err != nil { 301 return err 302 } 303 304 rec := new(TxRecord) 305 err = readRawTxRecord(&txHash, v, rec) 306 if err != nil { 307 return err 308 } 309 rec.Unpublished = existsUnpublished(ns, txHash[:]) 310 311 unmined[rec.Hash] = rec 312 return nil 313 }) 314 return unmined, err 315 } 316 317 // UnminedTxHashes returns the hashes of all transactions not known to have been 318 // mined in a block. 319 func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) { 320 return s.unminedTxHashes(ns) 321 } 322 323 func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) { 324 var hashes []*chainhash.Hash 325 err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error { 326 hash := new(chainhash.Hash) 327 err := readRawUnminedHash(k, hash) 328 if err == nil { 329 hashes = append(hashes, hash) 330 } 331 return err 332 }) 333 return hashes, err 334 } 335 336 // PruneUnmined removes unmined transactions that no longer belong in the 337 // unmined tx set. This includes: 338 // 339 // - Any transactions past a set expiry 340 // - Ticket purchases with a different ticket price than the passed stake 341 // difficulty 342 // - Votes that do not vote on the tip block 343 func (s *Store) PruneUnmined(dbtx walletdb.ReadWriteTx, stakeDiff int64) ([]*chainhash.Hash, error) { 344 ns := dbtx.ReadWriteBucket(wtxmgrBucketKey) 345 346 tipHash, tipHeight := s.MainChainTip(dbtx) 347 348 type removeTx struct { 349 tx wire.MsgTx 350 hash *chainhash.Hash 351 } 352 var toRemove []*removeTx 353 354 c := ns.NestedReadBucket(bucketUnmined).ReadCursor() 355 defer c.Close() 356 for k, v := c.First(); k != nil; k, v = c.Next() { 357 var tx wire.MsgTx 358 err := tx.Deserialize(bytes.NewReader(extractRawUnminedTx(v))) 359 if err != nil { 360 return nil, errors.E(errors.IO, err) 361 } 362 363 var expired, isTicketPurchase, isVote bool 364 switch { 365 case tx.Expiry != wire.NoExpiryValue && tx.Expiry <= uint32(tipHeight): 366 expired = true 367 case stake.IsSStx(&tx): 368 isTicketPurchase = true 369 if tx.TxOut[0].Value == stakeDiff { 370 continue 371 } 372 case stake.IsSSGen(&tx): 373 isVote = true 374 // This will never actually error 375 votedBlockHash, _ := stake.SSGenBlockVotedOn(&tx) 376 if votedBlockHash == tipHash { 377 continue 378 } 379 default: 380 continue 381 } 382 383 txHash, err := chainhash.NewHash(k) 384 if err != nil { 385 return nil, errors.E(errors.IO, err) 386 } 387 388 if expired { 389 log.Infof("Removing expired unmined transaction %v", txHash) 390 } else if isTicketPurchase { 391 log.Infof("Removing old unmined ticket purchase %v", txHash) 392 } else if isVote { 393 log.Infof("Removing missed or invalid vote %v", txHash) 394 } 395 396 toRemove = append(toRemove, &removeTx{tx, txHash}) 397 } 398 399 removed := make([]*chainhash.Hash, 0, len(toRemove)) 400 for _, r := range toRemove { 401 err := s.RemoveUnconfirmed(ns, &r.tx, r.hash) 402 if err != nil { 403 return removed, err 404 } 405 removed = append(removed, r.hash) 406 } 407 408 return removed, nil 409 }