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 }