github.com/decred/dcrlnd@v0.7.6/sweep/store.go (about) 1 package sweep 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 9 "github.com/decred/dcrd/chaincfg/chainhash" 10 "github.com/decred/dcrd/wire" 11 "github.com/decred/dcrlnd/kvdb" 12 ) 13 14 var ( 15 // lastTxBucketKey is the key that points to a bucket containing a 16 // single item storing the last published tx. 17 // 18 // maps: lastTxKey -> serialized_tx 19 lastTxBucketKey = []byte("sweeper-last-tx") 20 21 // lastTxKey is the fixed key under which the serialized tx is stored. 22 lastTxKey = []byte("last-tx") 23 24 // txHashesBucketKey is the key that points to a bucket containing the 25 // hashes of all sweep txes that were published successfully. 26 // 27 // maps: txHash -> empty slice 28 txHashesBucketKey = []byte("sweeper-tx-hashes") 29 30 // utxnChainPrefix is the bucket prefix for nursery buckets. 31 utxnChainPrefix = []byte("utxn") 32 33 // utxnHeightIndexKey is the sub bucket where the nursery stores the 34 // height index. 35 utxnHeightIndexKey = []byte("height-index") 36 37 // utxnFinalizedKndrTxnKey is a static key that can be used to locate 38 // the nursery finalized kindergarten sweep txn. 39 utxnFinalizedKndrTxnKey = []byte("finalized-kndr-txn") 40 41 byteOrder = binary.BigEndian 42 43 errNoTxHashesBucket = errors.New("tx hashes bucket does not exist") 44 ) 45 46 // SweeperStore stores published txes. 47 type SweeperStore interface { 48 // IsOurTx determines whether a tx is published by us, based on its 49 // hash. 50 IsOurTx(hash chainhash.Hash) (bool, error) 51 52 // NotifyPublishTx signals that we are about to publish a tx. 53 NotifyPublishTx(*wire.MsgTx) error 54 55 // GetLastPublishedTx returns the last tx that we called NotifyPublishTx 56 // for. 57 GetLastPublishedTx() (*wire.MsgTx, error) 58 59 // ListSweeps lists all the sweeps we have successfully published. 60 ListSweeps() ([]chainhash.Hash, error) 61 } 62 63 type sweeperStore struct { 64 db kvdb.Backend 65 } 66 67 // NewSweeperStore returns a new store instance. 68 func NewSweeperStore(db kvdb.Backend, chainHash *chainhash.Hash) ( 69 SweeperStore, error) { 70 71 err := kvdb.Update(db, func(tx kvdb.RwTx) error { 72 _, err := tx.CreateTopLevelBucket( 73 lastTxBucketKey, 74 ) 75 if err != nil { 76 return err 77 } 78 79 if tx.ReadWriteBucket(txHashesBucketKey) != nil { 80 return nil 81 } 82 83 txHashesBucket, err := tx.CreateTopLevelBucket( 84 txHashesBucketKey, 85 ) 86 if err != nil { 87 return err 88 } 89 90 // Use non-existence of tx hashes bucket as a signal to migrate 91 // nursery finalized txes. 92 err = migrateTxHashes(tx, txHashesBucket, chainHash) 93 94 return err 95 }, func() {}) 96 if err != nil { 97 return nil, err 98 } 99 100 return &sweeperStore{ 101 db: db, 102 }, nil 103 } 104 105 // migrateTxHashes migrates nursery finalized txes to the tx hashes bucket. This 106 // is not implemented as a database migration, to keep the downgrade path open. 107 func migrateTxHashes(tx kvdb.RwTx, txHashesBucket kvdb.RwBucket, 108 chainHash *chainhash.Hash) error { 109 110 log.Infof("Migrating UTXO nursery finalized TXIDs") 111 112 // Compose chain bucket key. 113 var b bytes.Buffer 114 if _, err := b.Write(utxnChainPrefix); err != nil { 115 return err 116 } 117 118 if _, err := b.Write(chainHash[:]); err != nil { 119 return err 120 } 121 122 // Get chain bucket if exists. 123 chainBucket := tx.ReadWriteBucket(b.Bytes()) 124 if chainBucket == nil { 125 return nil 126 } 127 128 // Retrieve the existing height index. 129 hghtIndex := chainBucket.NestedReadWriteBucket(utxnHeightIndexKey) 130 if hghtIndex == nil { 131 return nil 132 } 133 134 // Retrieve all heights. 135 err := hghtIndex.ForEach(func(k, v []byte) error { 136 heightBucket := hghtIndex.NestedReadWriteBucket(k) 137 if heightBucket == nil { 138 return nil 139 } 140 141 // Get finalized tx for height. 142 txBytes := heightBucket.Get(utxnFinalizedKndrTxnKey) 143 if txBytes == nil { 144 return nil 145 } 146 147 // Deserialize and skip tx if it cannot be deserialized. 148 tx := &wire.MsgTx{} 149 err := tx.Deserialize(bytes.NewReader(txBytes)) 150 if err != nil { 151 log.Warnf("Cannot deserialize utxn tx") 152 return nil 153 } 154 155 // Calculate hash. 156 hash := tx.TxHash() 157 158 // Insert utxn tx hash in hashes bucket. 159 log.Debugf("Inserting nursery tx %v in hash list "+ 160 "(height=%v)", hash, byteOrder.Uint32(k)) 161 162 return txHashesBucket.Put(hash[:], []byte{}) 163 }) 164 if err != nil { 165 return err 166 } 167 168 return nil 169 } 170 171 // NotifyPublishTx signals that we are about to publish a tx. 172 func (s *sweeperStore) NotifyPublishTx(sweepTx *wire.MsgTx) error { 173 return kvdb.Update(s.db, func(tx kvdb.RwTx) error { 174 lastTxBucket := tx.ReadWriteBucket(lastTxBucketKey) 175 if lastTxBucket == nil { 176 return errors.New("last tx bucket does not exist") 177 } 178 179 txHashesBucket := tx.ReadWriteBucket(txHashesBucketKey) 180 if txHashesBucket == nil { 181 return errNoTxHashesBucket 182 } 183 184 if sweepTx == nil { 185 if err := lastTxBucket.Delete(lastTxKey); err != nil { 186 return err 187 } 188 return nil 189 } 190 191 var b bytes.Buffer 192 if err := sweepTx.Serialize(&b); err != nil { 193 return err 194 } 195 196 if err := lastTxBucket.Put(lastTxKey, b.Bytes()); err != nil { 197 return err 198 } 199 200 hash := sweepTx.TxHash() 201 202 return txHashesBucket.Put(hash[:], []byte{}) 203 }, func() {}) 204 } 205 206 // GetLastPublishedTx returns the last tx that we called NotifyPublishTx 207 // for. 208 func (s *sweeperStore) GetLastPublishedTx() (*wire.MsgTx, error) { 209 var sweepTx *wire.MsgTx 210 211 err := kvdb.View(s.db, func(tx kvdb.RTx) error { 212 lastTxBucket := tx.ReadBucket(lastTxBucketKey) 213 if lastTxBucket == nil { 214 return errors.New("last tx bucket does not exist") 215 } 216 217 sweepTxRaw := lastTxBucket.Get(lastTxKey) 218 if sweepTxRaw == nil { 219 return nil 220 } 221 222 sweepTx = &wire.MsgTx{} 223 txReader := bytes.NewReader(sweepTxRaw) 224 if err := sweepTx.Deserialize(txReader); err != nil { 225 return fmt.Errorf("tx deserialize: %v", err) 226 } 227 228 return nil 229 }, func() { 230 sweepTx = nil 231 }) 232 if err != nil { 233 return nil, err 234 } 235 236 return sweepTx, nil 237 } 238 239 // IsOurTx determines whether a tx is published by us, based on its 240 // hash. 241 func (s *sweeperStore) IsOurTx(hash chainhash.Hash) (bool, error) { 242 var ours bool 243 244 err := kvdb.View(s.db, func(tx kvdb.RTx) error { 245 txHashesBucket := tx.ReadBucket(txHashesBucketKey) 246 if txHashesBucket == nil { 247 return errNoTxHashesBucket 248 } 249 250 ours = txHashesBucket.Get(hash[:]) != nil 251 252 return nil 253 }, func() { 254 ours = false 255 }) 256 if err != nil { 257 return false, err 258 } 259 260 return ours, nil 261 } 262 263 // ListSweeps lists all the sweep transactions we have in the sweeper store. 264 func (s *sweeperStore) ListSweeps() ([]chainhash.Hash, error) { 265 var sweepTxns []chainhash.Hash 266 267 if err := kvdb.View(s.db, func(tx kvdb.RTx) error { 268 txHashesBucket := tx.ReadBucket(txHashesBucketKey) 269 if txHashesBucket == nil { 270 return errNoTxHashesBucket 271 } 272 273 return txHashesBucket.ForEach(func(resKey, _ []byte) error { 274 txid, err := chainhash.NewHash(resKey) 275 if err != nil { 276 return err 277 } 278 279 sweepTxns = append(sweepTxns, *txid) 280 281 return nil 282 }) 283 }, func() { 284 sweepTxns = nil 285 }); err != nil { 286 return nil, err 287 } 288 289 return sweepTxns, nil 290 } 291 292 // Compile-time constraint to ensure sweeperStore implements SweeperStore. 293 var _ SweeperStore = (*sweeperStore)(nil)