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)