code.vegaprotocol.io/vega@v0.79.0/wallet/service/v1/endpoints.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package v1 17 18 import ( 19 "encoding/base64" 20 "encoding/hex" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "net/http" 26 "sort" 27 "time" 28 29 "code.vegaprotocol.io/vega/commands" 30 vfmt "code.vegaprotocol.io/vega/libs/fmt" 31 vgrand "code.vegaprotocol.io/vega/libs/rand" 32 api "code.vegaprotocol.io/vega/protos/vega/api/v1" 33 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 34 walletpb "code.vegaprotocol.io/vega/protos/vega/wallet/v1" 35 "code.vegaprotocol.io/vega/version" 36 nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types" 37 wcommands "code.vegaprotocol.io/vega/wallet/commands" 38 "code.vegaprotocol.io/vega/wallet/network" 39 "code.vegaprotocol.io/vega/wallet/wallet" 40 41 "github.com/golang/protobuf/jsonpb" 42 "github.com/julienschmidt/httprouter" 43 "go.uber.org/zap" 44 ) 45 46 const ( 47 TxnValidationFailure uint32 = 51 48 TxnDecodingFailure uint32 = 60 49 TxnInternalError uint32 = 70 50 TxnUnknownCommandError uint32 = 80 51 TxnSpamError uint32 = 89 52 ) 53 54 // CreateWalletRequest describes the request for CreateWallet. 55 type CreateWalletRequest struct { 56 Wallet string `json:"wallet"` 57 Passphrase string `json:"passphrase"` 58 } 59 60 const TXIDLENGTH = 20 61 62 func ParseCreateWalletRequest(r *http.Request) (*CreateWalletRequest, commands.Errors) { 63 errs := commands.NewErrors() 64 65 req := &CreateWalletRequest{} 66 if err := unmarshalBody(r, &req); err != nil { 67 return nil, errs.FinalAdd(err) 68 } 69 70 if len(req.Wallet) == 0 { 71 errs.AddForProperty("wallet", commands.ErrIsRequired) 72 } 73 74 if len(req.Passphrase) == 0 { 75 errs.AddForProperty("passphrase", commands.ErrIsRequired) 76 } 77 78 if !errs.Empty() { 79 return nil, errs 80 } 81 82 return req, errs 83 } 84 85 // CreateWalletResponse returns the authentication token and the auto-generated 86 // recovery phrase of the created wallet. 87 type CreateWalletResponse struct { 88 RecoveryPhrase string `json:"recoveryPhrase"` 89 Token string `json:"token"` 90 } 91 92 // ImportWalletRequest describes the request for ImportWallet. 93 type ImportWalletRequest struct { 94 Wallet string `json:"wallet"` 95 Passphrase string `json:"passphrase"` 96 RecoveryPhrase string `json:"recoveryPhrase"` 97 Version uint32 `json:"version"` 98 } 99 100 func ParseImportWalletRequest(r *http.Request) (*ImportWalletRequest, commands.Errors) { 101 errs := commands.NewErrors() 102 103 req := &ImportWalletRequest{} 104 if err := unmarshalBody(r, &req); err != nil { 105 return nil, errs.FinalAdd(err) 106 } 107 108 if len(req.Wallet) == 0 { 109 errs.AddForProperty("wallet", commands.ErrIsRequired) 110 } 111 112 if len(req.Passphrase) == 0 { 113 errs.AddForProperty("passphrase", commands.ErrIsRequired) 114 } 115 116 if len(req.RecoveryPhrase) == 0 { 117 errs.AddForProperty("recoveryPhrase", commands.ErrIsRequired) 118 } 119 120 if req.Version == 0 { 121 req.Version = wallet.LatestVersion 122 } 123 124 if !errs.Empty() { 125 return nil, errs 126 } 127 128 return req, errs 129 } 130 131 // LoginWalletRequest describes the request for CreateWallet, LoginWallet. 132 type LoginWalletRequest struct { 133 Wallet string `json:"wallet"` 134 Passphrase string `json:"passphrase"` 135 } 136 137 func ParseLoginWalletRequest(r *http.Request) (*LoginWalletRequest, commands.Errors) { 138 errs := commands.NewErrors() 139 140 req := &LoginWalletRequest{} 141 if err := unmarshalBody(r, &req); err != nil { 142 return nil, errs.FinalAdd(err) 143 } 144 145 if len(req.Wallet) == 0 { 146 errs.AddForProperty("wallet", commands.ErrIsRequired) 147 } 148 149 if len(req.Passphrase) == 0 { 150 errs.AddForProperty("passphrase", commands.ErrIsRequired) 151 } 152 153 if !errs.Empty() { 154 return nil, errs 155 } 156 157 return req, errs 158 } 159 160 // TaintKeyRequest describes the request for TaintKey. 161 type TaintKeyRequest struct { 162 Passphrase string `json:"passphrase"` 163 } 164 165 func ParseTaintKeyRequest(r *http.Request, keyID string) (*TaintKeyRequest, commands.Errors) { 166 errs := commands.NewErrors() 167 168 if len(keyID) == 0 { 169 errs.AddForProperty("keyid", commands.ErrIsRequired) 170 } 171 172 req := &TaintKeyRequest{} 173 if err := unmarshalBody(r, &req); err != nil { 174 return nil, errs.FinalAdd(err) 175 } 176 177 if len(req.Passphrase) == 0 { 178 errs.AddForProperty("passphrase", commands.ErrIsRequired) 179 } 180 181 if !errs.Empty() { 182 return nil, errs 183 } 184 185 return req, errs 186 } 187 188 // GenKeyPairRequest describes the request for GenerateKeyPair. 189 type GenKeyPairRequest struct { 190 Passphrase string `json:"passphrase"` 191 Meta []wallet.Metadata `json:"meta"` 192 } 193 194 func ParseGenKeyPairRequest(r *http.Request) (*GenKeyPairRequest, commands.Errors) { 195 errs := commands.NewErrors() 196 197 req := &GenKeyPairRequest{} 198 if err := unmarshalBody(r, &req); err != nil { 199 return nil, errs.FinalAdd(err) 200 } 201 202 if len(req.Passphrase) == 0 { 203 errs.AddForProperty("passphrase", commands.ErrIsRequired) 204 } 205 206 if !errs.Empty() { 207 return nil, errs 208 } 209 210 return req, errs 211 } 212 213 // UpdateMetaRequest describes the request for UpdateMetadata. 214 type UpdateMetaRequest struct { 215 Passphrase string `json:"passphrase"` 216 Meta []wallet.Metadata `json:"meta"` 217 } 218 219 func ParseUpdateMetaRequest(r *http.Request, keyID string) (*UpdateMetaRequest, commands.Errors) { 220 errs := commands.NewErrors() 221 222 if len(keyID) == 0 { 223 errs.AddForProperty("keyid", commands.ErrIsRequired) 224 } 225 226 req := &UpdateMetaRequest{} 227 if err := unmarshalBody(r, &req); err != nil { 228 return nil, errs.FinalAdd(err) 229 } 230 231 if len(req.Passphrase) == 0 { 232 errs.AddForProperty("passphrase", commands.ErrIsRequired) 233 } 234 235 if !errs.Empty() { 236 return nil, errs 237 } 238 239 return req, errs 240 } 241 242 // SignAnyRequest describes the request for SignAny. 243 type SignAnyRequest struct { 244 // InputData is the payload to generate a signature from. I should be 245 // base 64 encoded. 246 InputData string `json:"inputData"` 247 // PubKey is used to retrieve the private key to sign the InputDate. 248 PubKey string `json:"pubKey"` 249 250 decodedInputData []byte 251 } 252 253 func ParseSignAnyRequest(r *http.Request) (*SignAnyRequest, commands.Errors) { 254 errs := commands.NewErrors() 255 256 req := &SignAnyRequest{} 257 if err := unmarshalBody(r, &req); err != nil { 258 return nil, errs.FinalAdd(err) 259 } 260 261 if len(req.InputData) == 0 { 262 errs.AddForProperty("inputData", commands.ErrIsRequired) 263 } 264 decodedInputData, err := base64.StdEncoding.DecodeString(req.InputData) 265 if err != nil { 266 errs.AddForProperty("inputData", ErrShouldBeBase64Encoded) 267 } else { 268 req.decodedInputData = decodedInputData 269 } 270 271 if len(req.PubKey) == 0 { 272 errs.AddForProperty("pubKey", commands.ErrIsRequired) 273 } 274 275 if !errs.Empty() { 276 return nil, errs 277 } 278 279 return req, errs 280 } 281 282 // VerifyAnyRequest describes the request for VerifyAny. 283 type VerifyAnyRequest struct { 284 // InputData is the payload to be verified. It should be base64 encoded. 285 InputData string `json:"inputData"` 286 // Signature is the signature to check against the InputData. It should be 287 // base64 encoded. 288 Signature string `json:"signature"` 289 // PubKey is the public key used along the signature to check the InputData. 290 PubKey string `json:"pubKey"` 291 292 decodedInputData []byte 293 decodedSignature []byte 294 } 295 296 func ParseVerifyAnyRequest(r *http.Request) (*VerifyAnyRequest, commands.Errors) { 297 errs := commands.NewErrors() 298 299 req := &VerifyAnyRequest{} 300 if err := unmarshalBody(r, &req); err != nil { 301 return nil, errs.FinalAdd(err) 302 } 303 304 if len(req.InputData) == 0 { 305 errs.AddForProperty("inputData", commands.ErrIsRequired) 306 } else { 307 decodedInputData, err := base64.StdEncoding.DecodeString(req.InputData) 308 if err != nil { 309 errs.AddForProperty("inputData", ErrShouldBeBase64Encoded) 310 } else { 311 req.decodedInputData = decodedInputData 312 } 313 } 314 315 if len(req.Signature) == 0 { 316 errs.AddForProperty("signature", commands.ErrIsRequired) 317 } else { 318 decodedSignature, err := base64.StdEncoding.DecodeString(req.Signature) 319 if err != nil { 320 errs.AddForProperty("signature", ErrShouldBeBase64Encoded) 321 } else { 322 req.decodedSignature = decodedSignature 323 } 324 } 325 326 if len(req.PubKey) == 0 { 327 errs.AddForProperty("pubKey", commands.ErrIsRequired) 328 } 329 330 if !errs.Empty() { 331 return nil, errs 332 } 333 334 return req, nil 335 } 336 337 func ParseSubmitTransactionRequest(r *http.Request) (*walletpb.SubmitTransactionRequest, commands.Errors) { 338 errs := commands.NewErrors() 339 340 req := &walletpb.SubmitTransactionRequest{ 341 Propagate: true, 342 } 343 if err := jsonpb.Unmarshal(r.Body, req); err != nil { 344 return nil, errs.FinalAdd(err) 345 } 346 347 if errs = wcommands.CheckSubmitTransactionRequest(req); !errs.Empty() { 348 return nil, errs 349 } 350 351 return req, nil 352 } 353 354 // KeyResponse describes the response to a request that returns a single key. 355 type KeyResponse struct { 356 Key KeyKeyResponse `json:"key"` 357 } 358 359 type KeyKeyResponse struct { 360 Idx uint32 `json:"index"` 361 PublicKey string `json:"pub"` 362 KeyName string `json:"name"` 363 Algorithm wallet.Algorithm `json:"algorithm"` 364 Tainted bool `json:"tainted"` 365 MetadataList []wallet.Metadata `json:"meta"` 366 } 367 368 // KeysResponse describes the response to a request that returns a list of keys. 369 type KeysResponse struct { 370 Keys []KeyKeyResponse `json:"keys"` 371 } 372 373 // SignAnyResponse describes the response for SignAny. 374 type SignAnyResponse struct { 375 HexSignature string `json:"hexSignature"` 376 Base64Signature string `json:"base64Signature"` 377 } 378 379 // VerifyAnyResponse describes the response for VerifyAny. 380 type VerifyAnyResponse struct { 381 Valid bool `json:"success"` 382 } 383 384 // SuccessResponse describes the response to a request that returns a simple true/false answer. 385 type SuccessResponse struct { 386 Success bool `json:"success"` 387 } 388 389 // TokenResponse describes the response to a request that returns a token. 390 type TokenResponse struct { 391 Token string `json:"token"` 392 } 393 394 // VersionResponse describes the response to a request that returns app version info. 395 type VersionResponse struct { 396 Version string `json:"version"` 397 VersionHash string `json:"versionHash"` 398 } 399 400 // NetworkResponse describes the response to a request that returns app hosts info. 401 type NetworkResponse struct { 402 Network network.Network `json:"network"` 403 } 404 405 func (s *API) CreateWallet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 406 req, errs := ParseCreateWalletRequest(r) 407 if !errs.Empty() { 408 s.writeBadRequest(w, errs) 409 return 410 } 411 412 recoveryPhrase, err := s.handler.CreateWallet(req.Wallet, req.Passphrase) 413 if err != nil { 414 s.writeBadRequestErr(w, err) 415 return 416 } 417 418 token, err := s.auth.NewSession(req.Wallet) 419 if err != nil { 420 s.writeInternalError(w, err) 421 return 422 } 423 424 s.writeSuccess(w, CreateWalletResponse{ 425 RecoveryPhrase: recoveryPhrase, 426 Token: token, 427 }) 428 } 429 430 func (s *API) ImportWallet(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 431 req, errs := ParseImportWalletRequest(r) 432 if !errs.Empty() { 433 s.writeBadRequest(w, errs) 434 return 435 } 436 437 err := s.handler.ImportWallet(req.Wallet, req.Passphrase, req.RecoveryPhrase, req.Version) 438 if err != nil { 439 s.writeBadRequestErr(w, err) 440 return 441 } 442 443 token, err := s.auth.NewSession(req.Wallet) 444 if err != nil { 445 s.writeInternalError(w, err) 446 return 447 } 448 449 s.writeSuccess(w, TokenResponse{Token: token}) 450 } 451 452 func (s *API) Login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 453 req, errs := ParseLoginWalletRequest(r) 454 if !errs.Empty() { 455 s.writeBadRequest(w, errs) 456 return 457 } 458 459 err := s.handler.LoginWallet(req.Wallet, req.Passphrase) 460 if err != nil { 461 s.writeForbiddenError(w, err) 462 return 463 } 464 465 token, err := s.auth.NewSession(req.Wallet) 466 if err != nil { 467 s.writeInternalError(w, err) 468 return 469 } 470 471 s.writeSuccess(w, TokenResponse{Token: token}) 472 } 473 474 func (s *API) Revoke(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 475 token, err := extractToken(r) 476 if err != nil { 477 writeError(w, err) 478 return 479 } 480 481 if _, err := s.auth.Revoke(token); err != nil { 482 s.writeForbiddenError(w, err) 483 return 484 } 485 486 s.writeSuccess(w, nil) 487 } 488 489 func (s *API) GenerateKeyPair(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 490 token, err := extractToken(r) 491 if err != nil { 492 writeError(w, err) 493 return 494 } 495 496 req, errs := ParseGenKeyPairRequest(r) 497 if !errs.Empty() { 498 s.writeBadRequest(w, errs) 499 return 500 } 501 502 name, err := s.auth.VerifyToken(token) 503 if err != nil { 504 s.writeForbiddenError(w, err) 505 return 506 } 507 508 pubKey, err := s.handler.SecureGenerateKeyPair(name, req.Passphrase, req.Meta) 509 if err != nil { 510 if errors.Is(err, wallet.ErrWrongPassphrase) { 511 s.writeForbiddenError(w, err) 512 } else { 513 s.writeInternalError(w, err) 514 } 515 return 516 } 517 518 key, err := s.handler.GetPublicKey(name, pubKey) 519 if err != nil { 520 s.writeInternalError(w, err) 521 return 522 } 523 524 s.writeSuccess(w, KeyResponse{ 525 Key: KeyKeyResponse{ 526 Idx: key.Index(), 527 PublicKey: key.Key(), 528 KeyName: key.Name(), 529 Algorithm: wallet.Algorithm{ 530 Name: key.AlgorithmName(), 531 Version: key.AlgorithmVersion(), 532 }, 533 Tainted: key.IsTainted(), 534 MetadataList: key.Metadata(), 535 }, 536 }) 537 } 538 539 func (s *API) GetPublicKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 540 token, err := extractToken(r) 541 if err != nil { 542 writeError(w, err) 543 return 544 } 545 546 name, err := s.auth.VerifyToken(token) 547 if err != nil { 548 s.writeForbiddenError(w, err) 549 return 550 } 551 552 key, err := s.handler.GetPublicKey(name, ps.ByName("keyid")) 553 if err != nil { 554 var statusCode int 555 if errors.Is(err, wallet.ErrPubKeyDoesNotExist) { 556 statusCode = http.StatusNotFound 557 } else { 558 statusCode = http.StatusInternalServerError 559 } 560 s.writeError(w, newErrorResponse(err.Error()), statusCode) 561 return 562 } 563 564 s.writeSuccess(w, KeyResponse{ 565 Key: KeyKeyResponse{ 566 Idx: key.Index(), 567 PublicKey: key.Key(), 568 KeyName: key.Name(), 569 Algorithm: wallet.Algorithm{ 570 Name: key.AlgorithmName(), 571 Version: key.AlgorithmVersion(), 572 }, 573 Tainted: key.IsTainted(), 574 MetadataList: key.Metadata(), 575 }, 576 }) 577 } 578 579 func (s *API) ListPublicKeys(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 580 token, err := extractToken(r) 581 if err != nil { 582 writeError(w, err) 583 return 584 } 585 586 name, err := s.auth.VerifyToken(token) 587 if err != nil { 588 s.writeForbiddenError(w, err) 589 return 590 } 591 592 keys, err := s.handler.ListPublicKeys(name) 593 if err != nil { 594 s.writeInternalError(w, err) 595 return 596 } 597 598 res := make([]KeyKeyResponse, 0, len(keys)) 599 for _, key := range keys { 600 res = append(res, KeyKeyResponse{ 601 Idx: key.Index(), 602 PublicKey: key.Key(), 603 KeyName: key.Name(), 604 Algorithm: wallet.Algorithm{ 605 Name: key.AlgorithmName(), 606 Version: key.AlgorithmVersion(), 607 }, 608 Tainted: key.IsTainted(), 609 MetadataList: key.Metadata(), 610 }) 611 } 612 613 s.writeSuccess(w, KeysResponse{Keys: res}) 614 } 615 616 func (s *API) TaintKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 617 token, err := extractToken(r) 618 if err != nil { 619 writeError(w, err) 620 return 621 } 622 623 keyID := ps.ByName("keyid") 624 req, errs := ParseTaintKeyRequest(r, keyID) 625 if !errs.Empty() { 626 s.writeBadRequest(w, errs) 627 return 628 } 629 630 name, err := s.auth.VerifyToken(token) 631 if err != nil { 632 s.writeForbiddenError(w, err) 633 return 634 } 635 636 if err = s.handler.TaintKey(name, keyID, req.Passphrase); err != nil { 637 if errors.Is(err, wallet.ErrWrongPassphrase) { 638 s.writeForbiddenError(w, err) 639 } else { 640 s.writeInternalError(w, err) 641 } 642 return 643 } 644 645 s.writeSuccess(w, nil) 646 } 647 648 func (s *API) UpdateMeta(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 649 token, err := extractToken(r) 650 if err != nil { 651 writeError(w, err) 652 return 653 } 654 655 keyID := ps.ByName("keyid") 656 req, errs := ParseUpdateMetaRequest(r, keyID) 657 if !errs.Empty() { 658 s.writeBadRequest(w, errs) 659 return 660 } 661 662 name, err := s.auth.VerifyToken(token) 663 if err != nil { 664 s.writeForbiddenError(w, err) 665 return 666 } 667 668 if err = s.handler.UpdateMeta(name, keyID, req.Passphrase, req.Meta); err != nil { 669 if errors.Is(err, wallet.ErrWrongPassphrase) { 670 s.writeForbiddenError(w, err) 671 } else { 672 s.writeInternalError(w, err) 673 } 674 return 675 } 676 677 s.writeSuccess(w, nil) 678 } 679 680 func (s *API) SignAny(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 681 token, err := extractToken(r) 682 if err != nil { 683 writeError(w, err) 684 return 685 } 686 687 req, errs := ParseSignAnyRequest(r) 688 if !errs.Empty() { 689 s.writeBadRequest(w, errs) 690 return 691 } 692 693 name, err := s.auth.VerifyToken(token) 694 if err != nil { 695 s.writeForbiddenError(w, err) 696 return 697 } 698 699 signature, err := s.handler.SignAny(name, req.decodedInputData, req.PubKey) 700 if err != nil { 701 s.writeInternalError(w, err) 702 return 703 } 704 705 res := SignAnyResponse{ 706 HexSignature: hex.EncodeToString(signature), 707 Base64Signature: base64.StdEncoding.EncodeToString(signature), 708 } 709 710 s.writeSuccess(w, res) 711 } 712 713 func (s *API) VerifyAny(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 714 req, errs := ParseVerifyAnyRequest(r) 715 if !errs.Empty() { 716 s.writeBadRequest(w, errs) 717 return 718 } 719 720 verified, err := s.handler.VerifyAny(req.decodedInputData, req.decodedSignature, req.PubKey) 721 if err != nil { 722 s.writeInternalError(w, err) 723 return 724 } 725 726 s.writeSuccess(w, VerifyAnyResponse{Valid: verified}) 727 } 728 729 func (s *API) CheckTx(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 730 defer r.Body.Close() 731 732 token, err := extractToken(r) 733 if err != nil { 734 writeError(w, err) 735 return 736 } 737 738 name, err := s.auth.VerifyToken(token) 739 if err != nil { 740 s.writeForbiddenError(w, err) 741 return 742 } 743 744 req, errs := ParseSubmitTransactionRequest(r) 745 if !errs.Empty() { 746 s.writeBadRequest(w, errs) 747 return 748 } 749 750 stats, cltIdx, err := s.nodeForward.SpamStatistics(r.Context(), req.PubKey) 751 if err != nil { 752 s.writeInternalError(w, ErrCouldNotGetBlockHeight) 753 return 754 } 755 756 if stats.ChainId == "" { 757 s.writeInternalError(w, ErrCouldNotGetChainID) 758 return 759 } 760 st := convertSpamStatistics(stats) 761 tx, err := s.handler.SignTx(name, req, st.LastBlockHeight, stats.ChainId) 762 if err != nil { 763 s.writeInternalError(w, err) 764 return 765 } 766 767 // generate proof of work for the transaction 768 tx.Pow, err = s.spam.GenerateProofOfWork(req.PubKey, st) 769 if err != nil { 770 s.writeInternalError(w, err) 771 return 772 } 773 774 result, err := s.nodeForward.CheckTx(r.Context(), tx, cltIdx) 775 if err != nil { 776 s.writeInternalError(w, err) 777 return 778 } 779 780 s.writeSuccess(w, struct { 781 Success bool `json:"success"` 782 Code uint32 `json:"code"` 783 GasWanted int64 `json:"gas_wanted"` 784 GasUsed int64 `json:"gas_used"` 785 Tx *commandspb.Transaction `json:"tx"` 786 }{ 787 Success: result.Success, 788 Code: result.Code, 789 GasWanted: result.GasWanted, 790 GasUsed: result.GasUsed, 791 Tx: tx, 792 }) 793 } 794 795 func (s *API) SignTxSync(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 796 s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_SYNC) 797 } 798 799 func (s *API) SignTxCommit(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 800 s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_COMMIT) 801 } 802 803 func (s *API) SignTx(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 804 s.signTx(w, r, p, api.SubmitTransactionRequest_TYPE_ASYNC) 805 } 806 807 func (s *API) signTx(w http.ResponseWriter, r *http.Request, _ httprouter.Params, ty api.SubmitTransactionRequest_Type) { 808 defer r.Body.Close() 809 810 token, err := extractToken(r) 811 if err != nil { 812 writeError(w, err) 813 return 814 } 815 816 name, err := s.auth.VerifyToken(token) 817 if err != nil { 818 s.writeForbiddenError(w, err) 819 return 820 } 821 822 req, errs := ParseSubmitTransactionRequest(r) 823 if !errs.Empty() { 824 s.writeBadRequest(w, errs) 825 return 826 } 827 828 txID := vgrand.RandomStr(TXIDLENGTH) 829 receivedAt := time.Now() 830 approved, err := s.policy.Ask(req, txID, receivedAt) 831 if err != nil { 832 s.log.Error("couldn't get user consent", zap.Error(err)) 833 s.writeError(w, err, http.StatusServiceUnavailable) 834 return 835 } 836 837 if !approved { 838 s.log.Info("user rejected transaction signing request", zap.Any("request", req)) 839 s.writeError(w, ErrRejectedSignRequest, http.StatusUnauthorized) 840 return 841 } 842 s.log.Info("user approved transaction signing request", zap.Any("request", req)) 843 844 stats, cltIdx, err := s.nodeForward.SpamStatistics(r.Context(), req.PubKey) 845 ss := convertSpamStatistics(stats) 846 if err != nil || ss.ChainID == "" { 847 s.policy.Report(SentTransaction{ 848 TxID: txID, 849 Error: ErrCouldNotGetChainID, 850 }) 851 s.writeInternalError(w, ErrCouldNotGetChainID) 852 return 853 } 854 tx, err := s.handler.SignTx(name, req, ss.LastBlockHeight, ss.ChainID) 855 if err != nil { 856 s.policy.Report(SentTransaction{ 857 TxID: txID, 858 Error: err, 859 }) 860 s.writeInternalError(w, err) 861 return 862 } 863 864 // generate proof of work for the transaction 865 tx.Pow, err = s.spam.GenerateProofOfWork(req.PubKey, ss) 866 if err != nil { 867 s.policy.Report(SentTransaction{ 868 Tx: tx, 869 TxID: txID, 870 Error: err, 871 }) 872 s.writeInternalError(w, err) 873 return 874 } 875 sentAt := time.Now() 876 resp, err := s.nodeForward.SendTx(r.Context(), tx, ty, cltIdx) 877 if err != nil { 878 s.policy.Report(SentTransaction{ 879 Tx: tx, 880 TxID: txID, 881 Error: err, 882 SentAt: sentAt, 883 }) 884 s.writeInternalError(w, err) 885 return 886 } 887 if !resp.Success { 888 s.policy.Report(SentTransaction{ 889 Tx: tx, 890 TxID: txID, 891 Error: errors.New(resp.Data), 892 SentAt: sentAt, 893 }) 894 s.writeTxError(w, resp) 895 return 896 } 897 898 s.policy.Report(SentTransaction{ 899 TxHash: resp.TxHash, 900 TxID: txID, 901 Tx: tx, 902 SentAt: sentAt, 903 }) 904 905 s.writeSuccess(w, struct { 906 TxHash string `json:"txHash"` 907 ReceivedAt time.Time `json:"receivedAt"` 908 SentAt time.Time `json:"sentAt"` 909 TxID string `json:"txId"` 910 Tx *commandspb.Transaction `json:"tx"` 911 }{ 912 TxHash: resp.TxHash, 913 ReceivedAt: receivedAt, 914 SentAt: sentAt, 915 TxID: txID, 916 Tx: tx, 917 }) 918 } 919 920 func (s *API) Version(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 921 res := VersionResponse{ 922 Version: version.Get(), 923 VersionHash: version.GetCommitHash(), 924 } 925 926 s.writeSuccess(w, res) 927 } 928 929 func (s *API) GetNetwork(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 930 res := NetworkResponse{ 931 Network: *s.network, 932 } 933 s.writeSuccess(w, res) 934 } 935 936 func (s *API) Health(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 937 if err := s.nodeForward.HealthCheck(r.Context()); err != nil { 938 s.writeError(w, newErrorResponse(err.Error()), http.StatusFailedDependency) 939 return 940 } 941 s.writeSuccess(w, nil) 942 } 943 944 func (s *API) GetNetworkChainID(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 945 lastBlock, _, err := s.nodeForward.LastBlockHeightAndHash(r.Context()) 946 if err != nil { 947 s.writeError(w, newErrorResponse(err.Error()), http.StatusFailedDependency) 948 return 949 } 950 s.writeSuccess(w, struct { 951 ChainID string `json:"chainID"` 952 }{ 953 ChainID: lastBlock.ChainId, 954 }) 955 } 956 957 func (s *API) writeBadRequestErr(w http.ResponseWriter, err error) { 958 errs := commands.NewErrors() 959 s.writeErrors(w, http.StatusBadRequest, errs.FinalAdd(err)) 960 } 961 962 func (s *API) writeBadRequest(w http.ResponseWriter, errs commands.Errors) { 963 s.writeErrors(w, http.StatusBadRequest, errs) 964 } 965 966 func (s *API) writeErrors(w http.ResponseWriter, statusCode int, errs commands.Errors) { 967 w.Header().Set("Content-Type", "application/json") 968 w.WriteHeader(statusCode) 969 buf, err := json.Marshal(ErrorsResponse{Errors: errs}) 970 if err != nil { 971 s.log.Error("couldn't marshal errors", zap.String("error", vfmt.Escape(errs.Error()))) 972 w.WriteHeader(http.StatusInternalServerError) 973 return 974 } 975 if _, err := w.Write(buf); err != nil { 976 s.log.Error("couldn't write errors", zap.String("error", vfmt.Escape(errs.Error()))) 977 w.WriteHeader(http.StatusInternalServerError) 978 return 979 } 980 s.log.Info(fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode))) 981 } 982 983 func unmarshalBody(r *http.Request, into interface{}) error { 984 defer r.Body.Close() 985 body, err := io.ReadAll(r.Body) 986 if err != nil { 987 return ErrCouldNotReadRequest 988 } 989 if len(body) == 0 { 990 return nil 991 } 992 return json.Unmarshal(body, into) 993 } 994 995 func (s *API) writeForbiddenError(w http.ResponseWriter, e error) { 996 s.writeError(w, newErrorResponse(e.Error()), http.StatusForbidden) 997 } 998 999 func (s *API) writeInternalError(w http.ResponseWriter, e error) { 1000 s.writeError(w, newErrorResponse(e.Error()), http.StatusInternalServerError) 1001 } 1002 1003 func (s *API) writeTxError(w http.ResponseWriter, r *api.SubmitTransactionResponse) { 1004 var code int 1005 switch r.Code { 1006 case TxnSpamError: 1007 code = http.StatusTooManyRequests 1008 case TxnUnknownCommandError, TxnValidationFailure, TxnDecodingFailure: 1009 code = http.StatusBadRequest 1010 case TxnInternalError: 1011 code = http.StatusInternalServerError 1012 default: 1013 s.log.Error("unknown transaction code", zap.Uint32("code", r.Code)) 1014 code = http.StatusInternalServerError 1015 } 1016 s.writeError(w, newErrorResponse(r.Data), code) 1017 } 1018 1019 func (s *API) writeError(w http.ResponseWriter, e error, status int) { 1020 w.Header().Set("Content-Type", "application/json") 1021 w.WriteHeader(status) 1022 1023 buf, err := json.Marshal(e) 1024 if err != nil { 1025 s.log.Error("couldn't marshal error", zap.Error(err)) 1026 w.WriteHeader(http.StatusInternalServerError) 1027 return 1028 } 1029 _, err = w.Write(buf) 1030 if err != nil { 1031 s.log.Error("couldn't write error to HTTP response", zap.Error(err)) 1032 w.WriteHeader(http.StatusInternalServerError) 1033 return 1034 } 1035 s.log.Info(fmt.Sprintf("%d %s", status, http.StatusText(status))) 1036 } 1037 1038 func (s *API) writeSuccess(w http.ResponseWriter, data interface{}) { 1039 w.Header().Set("Content-Type", "application/json") 1040 w.WriteHeader(http.StatusOK) 1041 1042 if data == nil { 1043 s.log.Info(fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))) 1044 return 1045 } 1046 1047 buf, err := json.Marshal(data) 1048 if err != nil { 1049 s.log.Error("couldn't marshal error", zap.Error(err)) 1050 s.writeInternalError(w, fmt.Errorf("couldn't marshal error: %w", err)) 1051 return 1052 } 1053 1054 _, err = w.Write(buf) 1055 if err != nil { 1056 s.log.Error("couldn't write error to HTTP response", zap.Error(err)) 1057 s.writeInternalError(w, fmt.Errorf("couldn't write error to HTTP response: %w", err)) 1058 return 1059 } 1060 s.log.Info(fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))) 1061 } 1062 1063 // convertSpamStatistics takes us from the protos to the nodetypes version 1064 // the code is copied from the V2 API but is not worth the pain of trying to share 1065 // because V1 is disappearing soon anyway. 1066 func convertSpamStatistics(r *api.GetSpamStatisticsResponse) *nodetypes.SpamStatistics { 1067 proposals := map[string]uint64{} 1068 for _, st := range r.Statistics.Votes.Statistics { 1069 proposals[st.Proposal] = st.CountForEpoch 1070 } 1071 1072 blockStates := []nodetypes.PoWBlockState{} 1073 for _, b := range r.Statistics.Pow.BlockStates { 1074 blockStates = append(blockStates, nodetypes.PoWBlockState{ 1075 BlockHeight: b.BlockHeight, 1076 BlockHash: b.BlockHash, 1077 TransactionsSeen: b.TransactionsSeen, 1078 ExpectedDifficulty: b.ExpectedDifficulty, 1079 HashFunction: b.HashFunction, 1080 TxPerBlock: b.TxPerBlock, 1081 IncreasingDifficulty: b.IncreasingDifficulty, 1082 Difficulty: b.Difficulty, 1083 }) 1084 } 1085 1086 // sort by block-height so latest block is first 1087 sort.Slice(blockStates, func(i int, j int) bool { 1088 return blockStates[i].BlockHeight > blockStates[j].BlockHeight 1089 }) 1090 1091 var lastBlockHeight uint64 1092 if len(blockStates) > 0 { 1093 lastBlockHeight = blockStates[0].BlockHeight 1094 } 1095 return &nodetypes.SpamStatistics{ 1096 Proposals: toSpamStatistic(r.Statistics.Proposals), 1097 Delegations: toSpamStatistic(r.Statistics.Delegations), 1098 Transfers: toSpamStatistic(r.Statistics.Transfers), 1099 NodeAnnouncements: toSpamStatistic(r.Statistics.NodeAnnouncements), 1100 IssuesSignatures: toSpamStatistic(r.Statistics.IssueSignatures), 1101 CreateReferralSet: toSpamStatistic(r.Statistics.CreateReferralSet), 1102 UpdateReferralSet: toSpamStatistic(r.Statistics.UpdateReferralSet), 1103 ApplyReferralCode: toSpamStatistic(r.Statistics.ApplyReferralCode), 1104 Votes: &nodetypes.VoteSpamStatistics{ 1105 Proposals: proposals, 1106 MaxForEpoch: r.Statistics.Votes.MaxForEpoch, 1107 BannedUntil: r.Statistics.Votes.BannedUntil, 1108 }, 1109 PoW: &nodetypes.PoWStatistics{ 1110 PowBlockStates: blockStates, 1111 BannedUntil: r.Statistics.Pow.BannedUntil, 1112 PastBlocks: r.Statistics.Pow.NumberOfPastBlocks, 1113 }, 1114 ChainID: r.ChainId, 1115 EpochSeq: r.Statistics.EpochSeq, 1116 LastBlockHeight: lastBlockHeight, 1117 } 1118 } 1119 1120 func toSpamStatistic(st *api.SpamStatistic) *nodetypes.SpamStatistic { 1121 return &nodetypes.SpamStatistic{ 1122 CountForEpoch: st.CountForEpoch, 1123 MaxForEpoch: st.MaxForEpoch, 1124 BannedUntil: st.BannedUntil, 1125 } 1126 }