@@ -6,20 +6,25 @@ package webapi
6
6
7
7
import (
8
8
"bytes"
9
+ "encoding/base64"
9
10
"errors"
11
+ "fmt"
10
12
"io"
11
13
"net/http"
12
14
"strings"
15
+ "time"
13
16
14
- "github.com/decred/dcrd/blockchain/stake/v4"
15
- "github.com/decred/dcrd/chaincfg/chainhash"
16
17
"github.com/decred/vspd/rpc"
17
18
"github.com/gin-gonic/gin"
18
19
"github.com/gin-gonic/gin/binding"
19
20
"github.com/gorilla/sessions"
20
21
"github.com/jrick/wsrpc/v2"
21
22
)
22
23
24
+ // TicketSearchMessageFmt is the format for the message to be signed
25
+ // in order to search for a ticket using the vspd frontend.
26
+ const TicketSearchMessageFmt = "I want to check vspd ticket status for ticket %s on VSP %s on block window %d."
27
+
23
28
// withSession middleware adds a gorilla session to the request context for
24
29
// downstream handlers to make use of. Sessions are used by admin pages to
25
30
// maintain authentication status.
@@ -287,17 +292,9 @@ func vspAuth() gin.HandlerFunc {
287
292
hash := request .TicketHash
288
293
289
294
// Before hitting the db or any RPC, ensure this is a valid ticket hash.
290
- // A ticket hash should be 64 chars (MaxHashStringSize) and should parse
291
- // into a chainhash.Hash without error.
292
- if len (hash ) != chainhash .MaxHashStringSize {
293
- log .Errorf ("%s: Incorrect hash length (clientIP=%s): got %d, expected %d" ,
294
- funcName , c .ClientIP (), len (hash ), chainhash .MaxHashStringSize )
295
- sendErrorWithMsg ("invalid ticket hash" , errBadRequest , c )
296
- return
297
- }
298
- _ , err = chainhash .NewHashFromStr (hash )
299
- if err != nil {
300
- log .Errorf ("%s: Invalid hash (clientIP=%s): %v" , funcName , c .ClientIP (), err )
295
+ validticket , err := validateTicketHash (c , hash )
296
+ if ! validticket {
297
+ log .Errorf ("%s: %v" , funcName , err )
301
298
sendErrorWithMsg ("invalid ticket hash" , errBadRequest , c )
302
299
return
303
300
}
@@ -313,74 +310,153 @@ func vspAuth() gin.HandlerFunc {
313
310
// If the ticket was found in the database, we already know its
314
311
// commitment address. Otherwise we need to get it from the chain.
315
312
var commitmentAddress string
313
+ var isInvalid bool
314
+
316
315
if ticketFound {
317
316
commitmentAddress = ticket .CommitmentAddress
318
317
} else {
319
- dcrdClient := c .MustGet (dcrdKey ).(* rpc.DcrdRPC )
320
- dcrdErr := c .MustGet (dcrdErrorKey )
321
- if dcrdErr != nil {
322
- log .Errorf ("%s: could not get dcrd client: %v" , funcName , dcrdErr .(error ))
323
- sendError (errInternalError , c )
324
- return
325
- }
326
-
327
- resp , err := dcrdClient .GetRawTransaction (hash )
318
+ commitmentAddress , isInvalid , err = getCommitmentAddress (c , hash )
328
319
if err != nil {
329
- log .Errorf ("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v" , funcName , hash , err )
330
- sendError (errInternalError , c )
320
+ if isInvalid {
321
+ sendError (errInvalidTicket , c )
322
+ } else {
323
+ sendError (errInternalError , c )
324
+ }
325
+ log .Errorf ("%s: %v" , funcName , err )
331
326
return
332
327
}
328
+ }
333
329
334
- msgTx , err := decodeTransaction ( resp . Hex )
335
- if err != nil {
336
- log . Errorf ( "%s: Failed to decode ticket hex (ticketHash=%s): %v" , funcName , ticket . Hash , err )
337
- sendError ( errInternalError , c )
338
- return
339
- }
330
+ // Ensure a signature is provided.
331
+ signature := c . GetHeader ( "VSP-Client-Signature" )
332
+ if signature == "" {
333
+ sendErrorWithMsg ( "no VSP-Client-Signature header" , errBadRequest , c )
334
+ return
335
+ }
340
336
341
- err = isValidTicket (msgTx )
342
- if err != nil {
343
- log .Warnf ("%s: Invalid ticket (clientIP=%s, ticketHash=%s): %v" , funcName , c .ClientIP (), hash , err )
344
- sendError (errInvalidTicket , c )
345
- return
346
- }
337
+ // Validate request signature to ensure ticket ownership.
338
+ err = validateSignature (hash , commitmentAddress , signature , string (reqBytes ), c )
339
+ if err != nil {
340
+ log .Errorf ("%s: %v" , funcName , err )
341
+ sendError (errBadSignature , c )
342
+ return
343
+ }
347
344
348
- addr , err := stake .AddrFromSStxPkScrCommitment (msgTx .TxOut [1 ].PkScript , cfg .NetParams )
349
- if err != nil {
350
- log .Errorf ("%s: AddrFromSStxPkScrCommitment error (ticketHash=%s): %v" , funcName , hash , err )
351
- sendError (errInternalError , c )
352
- return
353
- }
345
+ // Add ticket information to context so downstream handlers don't need
346
+ // to access the db for it.
347
+ c .Set (ticketKey , ticket )
348
+ c .Set (knownTicketKey , ticketFound )
349
+ c .Set (commitmentAddressKey , commitmentAddress )
350
+ }
351
+
352
+ }
353
+
354
+ // ticketSearchAuth middleware reads the request form body and extracts the
355
+ // ticket hash and signature from the base64 string provided. The commitment
356
+ // address for the ticket is retrieved from the database if it is known, or
357
+ // it is retrieved from the chain if not.
358
+ // The middleware errors out if required information is not provided or the
359
+ // signature does not contain a message signed with the commitment
360
+ // address. Ticket information is added to the request context for downstream
361
+ // handlers to use.
362
+ func ticketSearchAuth () gin.HandlerFunc {
363
+ return func (c * gin.Context ) {
364
+ funcName := "ticketSearchAuth"
365
+
366
+ encodedString := c .PostForm ("encoded" )
367
+
368
+ // Get information added to context.
369
+ dcrdClient := c .MustGet (dcrdKey ).(* rpc.DcrdRPC )
370
+ dcrdErr := c .MustGet (dcrdErrorKey )
371
+ if dcrdErr != nil {
372
+ log .Warnf ("%s: %v" , funcName , dcrdErr .(error ))
373
+ c .Set (errorKey , errInternalError )
374
+ return
375
+ }
354
376
355
- commitmentAddress = addr .String ()
377
+ currentBlockHeader , err := dcrdClient .GetBestBlockHeader ()
378
+ if err != nil {
379
+ log .Errorf ("%s: Error getting best block header : %v" , funcName , err )
380
+ c .Set (errorKey , errInternalError )
381
+ return
356
382
}
357
383
358
- // Validate request signature to ensure ticket ownership.
359
- err = validateSignature (reqBytes , commitmentAddress , c )
384
+ // Average blocks per day for the current network.
385
+ blocksPerDay := (24 * time .Hour ) / cfg .NetParams .TargetTimePerBlock
386
+ blockWindow := int (currentBlockHeader .Height ) / int (blocksPerDay )
387
+
388
+ decodedByte , err := base64 .StdEncoding .DecodeString (encodedString )
389
+ if err != nil {
390
+ log .Errorf ("%s: Decoding form data error : %v" , funcName , err )
391
+ c .Set (errorKey , errBadRequest )
392
+ return
393
+ }
394
+
395
+ data := strings .Split (string (decodedByte ), ":" )
396
+ if len (data ) != 2 {
397
+ c .Set (errorKey , errBadRequest )
398
+ return
399
+ }
400
+
401
+ ticketHash := data [0 ]
402
+ signature := data [1 ]
403
+ vspURL := cfg .VSPUrl
404
+ messageSigned := fmt .Sprintf (TicketSearchMessageFmt , ticketHash , vspURL , blockWindow )
405
+
406
+ // Before hitting the db or any RPC, ensure this is a valid ticket hash.
407
+ validticket , err := validateTicketHash (c , ticketHash )
408
+ if ! validticket {
409
+ log .Errorf ("%s: %v" , funcName , err )
410
+ c .Set (errorKey , errInvalidTicket )
411
+ return
412
+ }
413
+
414
+ // Check if this ticket already appears in the database.
415
+ ticket , ticketFound , err := db .GetTicketByHash (ticketHash )
360
416
if err != nil {
361
- // Don't return an error straight away if sig validation fails -
362
- // first check if we have an alternate sign address for this ticket.
363
- altSigData , err := db .AltSignAddrData (hash )
417
+ log .Errorf ("%s: db.GetTicketByHash error (ticketHash=%s): %v" , funcName , ticketHash , err )
418
+ c .Set (errorKey , errInternalError )
419
+ return
420
+ }
421
+
422
+ if ! ticketFound {
423
+ log .Warnf ("%s: Unknown ticket (clientIP=%s)" , funcName , c .ClientIP ())
424
+ c .Set (errorKey , errUnknownTicket )
425
+ return
426
+ }
427
+
428
+ // If the ticket was found in the database, we already know its
429
+ // commitment address. Otherwise we need to get it from the chain.
430
+ var commitmentAddress string
431
+ var isInvalid bool
432
+ if ticketFound {
433
+ commitmentAddress = ticket .CommitmentAddress
434
+ } else {
435
+ commitmentAddress , isInvalid , err = getCommitmentAddress (c , ticketHash )
364
436
if err != nil {
365
- log .Errorf ("%s: db.AltSignAddrData failed (ticketHash=%s): %v" , funcName , hash , err )
366
- sendError (errInternalError , c )
437
+ if isInvalid {
438
+ c .Set (errorKey , errInvalidTicket )
439
+ } else {
440
+ c .Set (errorKey , errInternalError )
441
+ }
442
+ log .Errorf ("%s: %v" , funcName , err )
367
443
return
368
444
}
445
+ }
369
446
370
- // If we have no alternate sign address, or if validating with the
371
- // alt sign addr fails, return an error to the client.
372
- if altSigData == nil || validateSignature (reqBytes , altSigData .AltSignAddr , c ) != nil {
373
- log .Warnf ("%s: Bad signature (clientIP=%s, ticketHash=%s)" , funcName , c .ClientIP (), hash )
374
- sendError (errBadSignature , c )
375
- return
376
- }
447
+ // Validate request signature to ensure ticket ownership.
448
+ err = validateSignature (ticketHash , commitmentAddress , signature , messageSigned , c )
449
+ if err != nil {
450
+ log .Errorf ("%s: %v" , funcName , err )
451
+ c .Set (errorKey , errBadSignature )
452
+ return
377
453
}
378
454
379
455
// Add ticket information to context so downstream handlers don't need
380
456
// to access the db for it.
381
457
c .Set (ticketKey , ticket )
382
458
c .Set (knownTicketKey , ticketFound )
383
- c .Set (commitmentAddressKey , commitmentAddress )
459
+ c .Set (errorKey , nil )
384
460
}
385
461
386
462
}
0 commit comments