decred.org/dcrwallet/v3@v3.1.0/wallet/udb/vsp.go (about)

     1  // Copyright (c) 2020 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package udb
     6  
     7  import (
     8  	"bytes"
     9  
    10  	"decred.org/dcrwallet/v3/errors"
    11  	"decred.org/dcrwallet/v3/wallet/walletdb"
    12  	"github.com/decred/dcrd/chaincfg/chainhash"
    13  )
    14  
    15  var (
    16  	vspBucketKey       = []byte("vsp")
    17  	vspHostBucketKey   = []byte("vsphost")
    18  	vspPubKeyBucketKey = []byte("vsppubkey")
    19  )
    20  
    21  // FeeStatus represents the current fee status of a ticket.
    22  type FeeStatus int
    23  
    24  // FeeStatus types
    25  const (
    26  	// VSPFeeProcessStarted represents the state which process has being
    27  	// called but fee still not paid.
    28  	VSPFeeProcessStarted FeeStatus = iota
    29  	// VSPFeeProcessPaid represents the state where the process has being
    30  	// paid, but not published.
    31  	VSPFeeProcessPaid
    32  	VSPFeeProcessErrored
    33  	// VSPFeeProcessConfirmed represents the state where the fee has been
    34  	// confirmed by the VSP.
    35  	VSPFeeProcessConfirmed
    36  )
    37  
    38  type VSPTicket struct {
    39  	FeeHash     chainhash.Hash
    40  	FeeTxStatus uint32
    41  	VSPHostID   uint32
    42  	Host        string
    43  	PubKey      []byte
    44  }
    45  
    46  // SetVSPTicket sets a VSPTicket record into the db.
    47  func SetVSPTicket(dbtx walletdb.ReadWriteTx, ticketHash *chainhash.Hash, record *VSPTicket) error {
    48  	// Check for Host/Pubkey buckets
    49  	pubkey, err := GetVSPPubKey(dbtx, []byte(record.Host))
    50  	if err != nil && errors.Is(err, errors.NotExist) {
    51  		// Pubkey entry doesn't exist for that entry, so create new one for host
    52  		vspHostBucket := dbtx.ReadWriteBucket(vspHostBucketKey)
    53  		v := vspHostBucket.Get(rootVSPHostIndex)
    54  		vspHostID := byteOrder.Uint32(v)
    55  		vspHostID += 1 // Bump current index
    56  		err = SetVSPHost(dbtx, vspHostID, &VSPHost{
    57  			Host: []byte(record.Host),
    58  		})
    59  		if err != nil {
    60  			return err
    61  		}
    62  
    63  		vspIndexBytes := make([]byte, 4)
    64  		byteOrder.PutUint32(vspIndexBytes, vspHostID)
    65  		err = vspHostBucket.Put(rootVSPHostIndex, vspIndexBytes)
    66  		if err != nil {
    67  			return errors.E(errors.IO, err)
    68  		}
    69  
    70  		pubkey = &VSPPubKey{
    71  			ID:     vspHostID,
    72  			PubKey: record.PubKey,
    73  		}
    74  		err = SetVSPPubKey(dbtx, []byte(record.Host), pubkey)
    75  		if err != nil {
    76  			return err
    77  		}
    78  		record.VSPHostID = vspHostID
    79  	} else if err != nil {
    80  		return err
    81  	}
    82  
    83  	// If the pubkey from the record in the request differs from the pubkey
    84  	// in the database that is saved for the host, update the pubkey in the
    85  	// db but keep the vsphost id intact.
    86  	if !bytes.Equal(pubkey.PubKey, record.PubKey) {
    87  		err = SetVSPPubKey(dbtx, []byte(record.Host), &VSPPubKey{
    88  			ID:     pubkey.ID,
    89  			PubKey: record.PubKey,
    90  		})
    91  		if err != nil {
    92  			return err
    93  		}
    94  	}
    95  	record.VSPHostID = pubkey.ID
    96  
    97  	bucket := dbtx.ReadWriteBucket(vspBucketKey)
    98  	serializedRecord := serializeVSPTicket(record)
    99  
   100  	// Save the creation date of the store.
   101  	return bucket.Put(ticketHash[:], serializedRecord)
   102  }
   103  
   104  // GetVSPTicket gets a specific ticket by its hash.
   105  func GetVSPTicket(dbtx walletdb.ReadTx, tickethash chainhash.Hash) (*VSPTicket, error) {
   106  	bucket := dbtx.ReadBucket(vspBucketKey)
   107  	serializedTicket := bucket.Get(tickethash[:])
   108  	if serializedTicket == nil {
   109  		err := errors.Errorf("no VSP info for ticket %v", &tickethash)
   110  		return nil, errors.E(errors.NotExist, err)
   111  	}
   112  	ticket := deserializeVSPTicket(serializedTicket)
   113  
   114  	host, err := GetVSPHost(dbtx, ticket.VSPHostID)
   115  	if err != nil && !errors.Is(err, errors.NotExist) {
   116  		// Only error out if it's not a 'not exist error'
   117  		return nil, err
   118  	}
   119  	if host != nil && len(host.Host) > 0 {
   120  		// If the stored host is not empty then get the saved pubkey as well.
   121  		ticket.Host = string(host.Host)
   122  		pubkey, err := GetVSPPubKey(dbtx, host.Host)
   123  		if err != nil && !errors.Is(err, errors.NotExist) {
   124  			return nil, err
   125  		}
   126  		if pubkey != nil {
   127  			// If pubkey was found then set it, otherwise skip it.
   128  			ticket.PubKey = pubkey.PubKey
   129  		}
   130  	}
   131  	return ticket, nil
   132  }
   133  
   134  // GetVSPTicketsByFeeStatus gets all vsp tickets which have
   135  // FeeTxStatus == feeStatus.
   136  func GetVSPTicketsByFeeStatus(dbtx walletdb.ReadTx, feeStatus int) (map[chainhash.Hash]*VSPTicket, error) {
   137  	bucket := dbtx.ReadBucket(vspBucketKey)
   138  	tickets := make(map[chainhash.Hash]*VSPTicket)
   139  	err := bucket.ForEach(func(k, v []byte) error {
   140  		ticket := deserializeVSPTicket(v)
   141  		if int(ticket.FeeTxStatus) == feeStatus {
   142  			var hash chainhash.Hash
   143  			hash.SetBytes(k)
   144  			tickets[hash] = ticket
   145  		}
   146  		return nil
   147  	})
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return tickets, nil
   152  }
   153  
   154  // deserializeUserTicket deserializes the passed serialized user
   155  // ticket information.
   156  func deserializeVSPTicket(serializedTicket []byte) *VSPTicket {
   157  	// ticket stores hash size and an uint32 representing the fee processment
   158  	// status.
   159  	curPos := 0
   160  	vspTicket := &VSPTicket{}
   161  	copy(vspTicket.FeeHash[:], serializedTicket[curPos:hashSize])
   162  	curPos += hashSize
   163  	vspTicket.FeeTxStatus = byteOrder.Uint32(serializedTicket[curPos : curPos+4])
   164  	curPos += 4
   165  	vspTicket.VSPHostID = byteOrder.Uint32(serializedTicket[curPos : curPos+4])
   166  
   167  	return vspTicket
   168  }
   169  
   170  // serializeUserTicket returns the serialization of a single stake pool
   171  // user ticket.
   172  func serializeVSPTicket(record *VSPTicket) []byte {
   173  	// ticket hash size + fee processment status + host ID
   174  	buf := make([]byte, hashSize+4+4)
   175  	curPos := 0
   176  	// Write the fee hash.
   177  	copy(buf[curPos:curPos+hashSize], record.FeeHash[:])
   178  	curPos += hashSize
   179  	// Write the fee tx status
   180  	byteOrder.PutUint32(buf[curPos:curPos+4], record.FeeTxStatus)
   181  	curPos += 4
   182  	// Write the VSP Host db ID
   183  	byteOrder.PutUint32(buf[curPos:curPos+4], record.VSPHostID)
   184  
   185  	return buf
   186  }
   187  
   188  type VSPHost struct {
   189  	Host []byte
   190  }
   191  
   192  // SetVSPHost sets a VSPHost record into the db.
   193  func SetVSPHost(dbtx walletdb.ReadWriteTx, id uint32, record *VSPHost) error {
   194  	bucket := dbtx.ReadWriteBucket(vspHostBucketKey)
   195  	serializedRecord := serializeVSPHost(record)
   196  
   197  	k := make([]byte, 4)
   198  	byteOrder.PutUint32(k, id)
   199  
   200  	return bucket.Put(k, serializedRecord)
   201  }
   202  
   203  // GetVSPHost gets a specific ticket by the id.
   204  func GetVSPHost(dbtx walletdb.ReadTx, id uint32) (*VSPHost, error) {
   205  	k := make([]byte, 4)
   206  	byteOrder.PutUint32(k, id)
   207  	bucket := dbtx.ReadBucket(vspHostBucketKey)
   208  	serializedHost := bucket.Get(k)
   209  	if serializedHost == nil {
   210  		err := errors.Errorf("no VSP host info for id %v", &id)
   211  		return nil, errors.E(errors.NotExist, err)
   212  	}
   213  	host := deserializeVSPHost(serializedHost)
   214  
   215  	return host, nil
   216  }
   217  
   218  // deserializeVSPHost deserializes the passed serialized vsp host information.
   219  func deserializeVSPHost(serializedHost []byte) *VSPHost {
   220  	curPos := 0
   221  	VSPHost := &VSPHost{}
   222  	hostLength := byteOrder.Uint32(serializedHost[curPos : curPos+4])
   223  	curPos += 4
   224  	VSPHost.Host = serializedHost[curPos : curPos+int(hostLength)]
   225  
   226  	return VSPHost
   227  }
   228  
   229  // serializeVSPHost serializes a vsp host record into a byte array.
   230  func serializeVSPHost(record *VSPHost) []byte {
   231  	// host length + host
   232  	buf := make([]byte, 4+len(record.Host))
   233  	curPos := 0
   234  	// Write the host length first
   235  	byteOrder.PutUint32(buf[curPos:curPos+4], uint32(len(record.Host)))
   236  	curPos += 4
   237  	// Then write the host based on the length provided.
   238  	copy(buf[curPos:curPos+len(record.Host)], record.Host)
   239  
   240  	return buf
   241  }
   242  
   243  type VSPPubKey struct {
   244  	ID     uint32
   245  	PubKey []byte
   246  }
   247  
   248  // SetVSPPubKey sets a VSPPubKey record into the db.
   249  func SetVSPPubKey(dbtx walletdb.ReadWriteTx, host []byte, record *VSPPubKey) error {
   250  	bucket := dbtx.ReadWriteBucket(vspPubKeyBucketKey)
   251  	serializedRecord := serializeVSPPubKey(record)
   252  
   253  	return bucket.Put(host, serializedRecord)
   254  }
   255  
   256  // GetVSPPubKey gets a specific ticket by the host.
   257  func GetVSPPubKey(dbtx walletdb.ReadTx, host []byte) (*VSPPubKey, error) {
   258  	bucket := dbtx.ReadBucket(vspPubKeyBucketKey)
   259  	serializedPubKey := bucket.Get(host)
   260  	if serializedPubKey == nil {
   261  		err := errors.Errorf("no VSP pubkey info for host %v", &host)
   262  		return nil, errors.E(errors.NotExist, err)
   263  	}
   264  	pubkey := deserializeVSPPubKey(serializedPubKey)
   265  	return pubkey, nil
   266  }
   267  
   268  // deserializeVSPPubKey deserializes the passed serialized vsp host information.
   269  func deserializeVSPPubKey(serializedPubKey []byte) *VSPPubKey {
   270  	curPos := 0
   271  	VSPPubKey := &VSPPubKey{}
   272  	VSPPubKey.ID = byteOrder.Uint32(serializedPubKey[curPos : curPos+4])
   273  	curPos += 4
   274  	pubkeyLength := byteOrder.Uint32(serializedPubKey[curPos : curPos+4])
   275  	curPos += 4
   276  	VSPPubKey.PubKey = serializedPubKey[curPos : curPos+int(pubkeyLength)]
   277  
   278  	return VSPPubKey
   279  }
   280  
   281  // serializeVSPPubKey serializes a vsp host record into a byte array.
   282  func serializeVSPPubKey(record *VSPPubKey) []byte {
   283  	// id + pubkey length + pubkey
   284  	buf := make([]byte, 4+4+len(record.PubKey))
   285  	curPos := 0
   286  	// Write the id first
   287  	byteOrder.PutUint32(buf[curPos:curPos+4], record.ID)
   288  	curPos += 4
   289  	// Write the pubkey length
   290  	byteOrder.PutUint32(buf[curPos:curPos+4], uint32(len(record.PubKey)))
   291  	curPos += 4
   292  	// Then write the pubkey based on the length provided.
   293  	copy(buf[curPos:curPos+len(record.PubKey)], record.PubKey)
   294  
   295  	return buf
   296  }