// Validate validates if request fields are valid. Useful when checking if a request is correct. func (request *SendRequest) Validate() error { err := request.FormRequest.CheckRequired(request) if err != nil { return err } _, err = keypair.Parse(request.Source) if err != nil { return protocols.NewInvalidParameterError("source", request.Source) } if !validateStellarAddress(request.Sender) { return protocols.NewInvalidParameterError("sender", request.Sender) } if !validateStellarAddress(request.Destination) { return protocols.NewInvalidParameterError("destination", request.Destination) } _, err = keypair.Parse(request.AssetIssuer) if err != nil { return protocols.NewInvalidParameterError("asset_issuer", request.AssetIssuer) } return nil }
// Validate validates if request fields are valid. Useful when checking if a request is correct. func (request *PaymentRequest) Validate() error { err := request.FormRequest.CheckRequired(request) if err != nil { return err } if request.Source != "" { _, err = keypair.Parse(request.Source) if err != nil { return protocols.NewInvalidParameterError("source", request.Source) } } // Memo if request.MemoType == "" && request.Memo != "" { return protocols.NewMissingParameter("memo_type") } if request.MemoType != "" && request.Memo == "" { return protocols.NewMissingParameter("memo") } // Destination Asset if request.AssetCode == "" && request.AssetIssuer != "" { return protocols.NewMissingParameter("asset_code") } if request.AssetCode != "" && request.AssetIssuer == "" { return protocols.NewMissingParameter("asset_issuer") } if request.AssetIssuer != "" { _, err := keypair.Parse(request.AssetIssuer) if err != nil { return protocols.NewInvalidParameterError("asset_issuer", request.AssetIssuer) } } // Send Asset if request.SendAssetCode == "" && request.SendAssetIssuer != "" { return protocols.NewMissingParameter("send_asset_code") } if request.SendAssetCode != "" && request.SendAssetIssuer == "" { return protocols.NewMissingParameter("send_asset_issuer") } if request.SendAssetIssuer != "" { _, err := keypair.Parse(request.SendAssetIssuer) if err != nil { return protocols.NewInvalidParameterError("send_asset_issuer", request.SendAssetIssuer) } } return nil }
// Validate validates if request fields are valid. Useful when checking if a request is correct. func (request *AuthorizeRequest) Validate(allowedAssets []config.Asset, issuingAccountID string) error { err := request.FormRequest.CheckRequired(request) if err != nil { return err } _, err = keypair.Parse(request.AccountID) if err != nil { return protocols.NewInvalidParameterError("account_id", request.AccountID) } // Is asset allowed? allowed := false for _, asset := range allowedAssets { if asset.Code == request.AssetCode && asset.Issuer == issuingAccountID { allowed = true break } } if !allowed { return protocols.NewInvalidParameterError("asset_code", request.AssetCode) } return nil }
// IsValidAccountID returns true if account ID is valid func IsValidAccountID(accountID string) bool { _, err := keypair.Parse(accountID) if err != nil { return false } return true }
func setAccountId(addressOrSeed string, aid *xdr.AccountId) error { kp, err := keypair.Parse(addressOrSeed) if err != nil { return err } return aid.SetAddress(kp.Address()) }
func setAccountId(addressOrSeed string, aid *xdr.AccountId) error { kp, err := keypair.Parse(addressOrSeed) if err != nil { return err } if aid == nil { return errors.New("aid is nil in setAccountId") } return aid.SetAddress(kp.Address()) }
// Verify verifies if signature is a valid signature of message signed by publicKey. func (s *SignerVerifier) Verify(publicKey string, message, signature []byte) error { kp, err := keypair.Parse(publicKey) if err != nil { return err } err = kp.Verify(message, signature) if err != nil { return err } return nil }
// Sign signs message using secretSeed. Returns base64-encoded signature. func (s *SignerVerifier) Sign(secretSeed string, message []byte) (string, error) { kp, err := keypair.Parse(secretSeed) if err != nil { return "", err } signature, err := kp.Sign(message) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(signature), nil }
func (this *MergeAccount) checkSeed(seed, srcAddr string) bool { pk, err := keypair.Parse(seed) if err == nil { if pk.Address() == srcAddr { return true } ConsoleColor.Printf(ConsoleColor.C_RED, this.infoStrings[this.languageIndex][MA_INFO_SEED_AND_ADDR_IS_NOT_PAIR]+"\r\n") // fmt.Printf(this.infoStrings[this.languageIndex][MA_INFO_SEED_AND_ADDR_IS_NOT_PAIR] + "\r\n") } else { ConsoleColor.Println(ConsoleColor.C_RED, err) // fmt.Println(err) } return false }
// ToComplianceSendRequest transforms PaymentRequest to compliance.SendRequest func (request *PaymentRequest) ToComplianceSendRequest() compliance.SendRequest { sourceKeypair, _ := keypair.Parse(request.Source) return compliance.SendRequest{ // Compliance does not sign transaction, it just needs public key Source: sourceKeypair.Address(), Sender: request.Sender, Destination: request.Destination, Amount: request.Amount, AssetCode: request.AssetCode, AssetIssuer: request.AssetIssuer, SendMax: request.SendMax, SendAssetCode: request.SendAssetCode, SendAssetIssuer: request.SendAssetIssuer, Path: request.Path, ExtraMemo: request.ExtraMemo, } }
// LoadAccount loads currect state of Stellar account func (ts *TransactionSubmitter) LoadAccount(seed string) (account *Account, err error) { account = &Account{} account.Keypair, err = keypair.Parse(seed) if err != nil { ts.log.Print("Invalid seed") return } accountResponse, err := ts.Horizon.LoadAccount(account.Keypair.Address()) if err != nil { return } account.Seed = seed account.SequenceNumber, err = strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) return }
func NewPaymentListener( config *config.Config, entityManager db.EntityManagerInterface, horizon horizon.HorizonInterface, repository db.RepositoryInterface, now func() time.Time, ) (pl PaymentListener, err error) { pl.config = config pl.entityManager = entityManager pl.horizon = horizon pl.repository = repository pl.issuingAccount, err = keypair.Parse(*config.Accounts.IssuingSeed) pl.now = now pl.log = logrus.WithFields(logrus.Fields{ "service": "PaymentListener", }) return }
// MutateTransactionEnvelope adds a signature to the provided envelope func (m Sign) MutateTransactionEnvelope(txe *TransactionEnvelopeBuilder) error { hash, err := txe.child.Hash() if err != nil { return err } kp, err := keypair.Parse(m.Seed) if err != nil { return err } sig, err := kp.SignDecorated(hash[:]) if err != nil { return err } txe.E.Signatures = append(txe.E.Signatures, sig) return nil }
// Validate validates config and returns error if any of config values is incorrect func (c *Config) Validate() (err error) { if c.Port == nil { err = errors.New("port param is required") return } if c.Horizon == "" { err = errors.New("horizon param is required") return } _, err = url.Parse(c.Horizon) if err != nil { err = errors.New("Cannot parse horizon param") return } if c.NetworkPassphrase == "" { err = errors.New("network_passphrase param is required") return } var dbURL *url.URL dbURL, err = url.Parse(c.Database.URL) if err != nil { err = errors.New("Cannot parse database.url param") return } switch c.Database.Type { case "mysql": // Add `parseTime=true` param to mysql url query := dbURL.Query() query.Set("parseTime", "true") dbURL.RawQuery = query.Encode() c.Database.URL = dbURL.String() case "postgres": break case "": // Allow to start gateway server with a single endpoint: /payment break default: err = errors.New("Invalid database.type param") return } if c.Accounts.AuthorizingSeed != "" { _, err = keypair.Parse(c.Accounts.AuthorizingSeed) if err != nil { err = errors.New("accounts.authorizing_seed is invalid") return } } if c.Accounts.BaseSeed != "" { _, err = keypair.Parse(c.Accounts.BaseSeed) if err != nil { err = errors.New("accounts.base_seed is invalid") return } } if c.Accounts.IssuingAccountID != "" { _, err = keypair.Parse(c.Accounts.IssuingAccountID) if err != nil { err = errors.New("accounts.issuing_account_id is invalid") return } } if c.Accounts.ReceivingAccountID != "" { _, err = keypair.Parse(c.Accounts.ReceivingAccountID) if err != nil { err = errors.New("accounts.receiving_account_id is invalid") return } } if c.Callbacks.Receive != "" { _, err = url.Parse(c.Callbacks.Receive) if err != nil { err = errors.New("Cannot parse callbacks.receive param") return } } if c.Callbacks.Error != "" { _, err = url.Parse(c.Callbacks.Error) if err != nil { err = errors.New("Cannot parse callbacks.error param") return } } return }
func (rh *RequestHandler) Send(w http.ResponseWriter, r *http.Request) { destination := r.PostFormValue("destination") assetCode := r.PostFormValue("asset_code") amount := r.PostFormValue("amount") destinationObject, err := rh.AddressResolver.Resolve(destination) if err != nil { log.WithFields(log.Fields{"destination": destination}).Print("Cannot resolve address") errorBadRequest(w, errorResponseString("invalid_destination", "Cannot resolve destination")) return } _, err = keypair.Parse(destinationObject.AccountId) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountId}).Print("Invalid AccountId in destination") errorBadRequest(w, errorResponseString("invalid_destination", "destination parameter is invalid")) return } if !rh.isAssetAllowed(assetCode) { log.Print("Asset code not allowed: ", assetCode) errorBadRequest(w, errorResponseString("invalid_asset_code", "Given assetCode not allowed")) return } issuingKeypair, err := keypair.Parse(*rh.Config.Accounts.IssuingSeed) if err != nil { log.Print("Invalid issuingSeed") errorServerError(w) return } operationMutator := b.Payment( b.Destination{destinationObject.AccountId}, b.CreditAmount{assetCode, issuingKeypair.Address(), amount}, ) if operationMutator.Err != nil { log.Print("Error creating operationMutator ", operationMutator.Err) errorServerError(w) return } memoType := r.PostFormValue("memo_type") memo := r.PostFormValue("memo") if !(((memoType == "") && (memo == "")) || ((memoType != "") && (memo != ""))) { log.Print("Missing one of memo params.") errorBadRequest(w, errorResponseString("memo_missing_param", "When passing memo both params: `memo_type`, `memo` are required")) return } if destinationObject.MemoType != nil { if memoType != "" { log.Print("Memo given in request but federation returned memo fields.") errorBadRequest(w, errorResponseString("cannot_use_memo", "Memo given in request but federation returned memo fields")) return } memoType = *destinationObject.MemoType memo = *destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") errorBadRequest(w, errorResponseString("cannot_convert_memo_id", "Cannot convert memo_id value")) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = b.MemoText{memo} default: log.Print("Not supported memo type: ", memoType) errorBadRequest(w, errorResponseString("memo_not_supported", "Not supported memo type")) return } submitResponse, err := rh.TransactionSubmitter.SubmitTransaction( *rh.Config.Accounts.IssuingSeed, operationMutator, memoMutator, ) if err != nil { log.Print("Error submitting transaction ", err) errorServerError(w) return } if submitResponse.Errors != nil { var errorString string if submitResponse.Errors.OperationErrorCode != "" { switch submitResponse.Errors.OperationErrorCode { case "payment_malformed": errorString = errorResponseString( "payment_malformed", "Operation is malformed.", ) case "payment_underfunded": errorString = errorResponseString( "payment_underfunded", "Not enough funds to send this transaction.", ) case "payment_src_no_trust": errorString = errorResponseString( "payment_src_no_trust", "No trustline on source account.", ) case "payment_src_not_authorized": errorString = errorResponseString( "payment_src_not_authorized", "Source not authorized to transfer.", ) case "payment_no_destination": errorString = errorResponseString( "payment_no_destination", "Destination account does not exist.", ) case "payment_no_trust": errorString = errorResponseString( "payment_no_trust", "Destination missing a trust line for asset.", ) case "payment_not_authorized": errorString = errorResponseString( "payment_not_authorized", "Destination not authorized to trust asset. It needs to be allowed first by using /authorize endpoint.", ) case "payment_line_full": errorString = errorResponseString( "payment_line_full", "Sending this payment would make a destination go above their limit.", ) case "payment_no_issuer": errorString = errorResponseString( "payment_no_issuer", "Missing issuer on asset.", ) default: errorServerError(w) return } } else if submitResponse.Errors.TransactionErrorCode != "" { switch submitResponse.Errors.TransactionErrorCode { case "transaction_bad_seq": errorString = errorResponseString( "transaction_bad_seq", "Bad Sequence. Please, try again.", ) default: errorServerError(w) return } } errorBadRequest(w, errorString) return } json, err := json.MarshalIndent(submitResponse, "", " ") if err != nil { errorServerError(w) return } w.Write(json) }
// Payment implements /payment endpoint func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { request := &bridge.PaymentRequest{} request.FromRequest(r) err := request.Validate() if err != nil { errorResponse := err.(*protocols.ErrorResponse) log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } if request.Source == "" { request.Source = rh.Config.Accounts.BaseSeed } sourceKeypair, _ := keypair.Parse(request.Source) var submitResponse horizon.SubmitTransactionResponse var submitError error if request.ExtraMemo != "" && rh.Config.Compliance != "" { // Compliance server part sendRequest := request.ToComplianceSendRequest() resp, err := rh.Client.PostForm( rh.Config.Compliance+"/send", sendRequest.ToValues(), ) if err != nil { log.WithFields(log.Fields{"err": err}).Error("Error sending request to compliance server") server.Write(w, protocols.InternalServerError) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("Error reading compliance server response") server.Write(w, protocols.InternalServerError) return } if resp.StatusCode != 200 { log.WithFields(log.Fields{ "status": resp.StatusCode, "body": string(body), }).Error("Error response from compliance server") server.Write(w, protocols.InternalServerError) return } var complianceSendResponse compliance.SendResponse err = json.Unmarshal(body, &complianceSendResponse) if err != nil { log.Error("Error unmarshalling from compliance server") server.Write(w, protocols.InternalServerError) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusPending || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusPending { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response pending") server.Write(w, bridge.NewPaymentPendingError(complianceSendResponse.AuthResponse.Pending)) return } if complianceSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusDenied || complianceSendResponse.AuthResponse.TxStatus == compliance.AuthStatusDenied { log.WithFields(log.Fields{"response": complianceSendResponse}).Info("Compliance response denied") server.Write(w, bridge.PaymentDenied) return } var tx xdr.Transaction err = xdr.SafeUnmarshalBase64(complianceSendResponse.TransactionXdr, &tx) if err != nil { log.Error("Error unmarshalling transaction returned by compliance server") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.TransactionSubmitter.SignAndSubmitRawTransaction(request.Source, &tx) } else { // Payment without compliance server destinationObject, _, err := rh.FederationResolver.Resolve(request.Destination) if err != nil { log.WithFields(log.Fields{"destination": request.Destination, "err": err}).Print("Cannot resolve address") server.Write(w, bridge.PaymentCannotResolveDestination) return } _, err = keypair.Parse(destinationObject.AccountID) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountID}).Print("Invalid AccountId in destination") server.Write(w, protocols.NewInvalidParameterError("destination", request.Destination)) return } var payWithMutator *b.PayWithPath if request.SendMax != "" { // Path payment var sendAsset b.Asset if request.SendAssetCode == "" && request.SendAssetIssuer == "" { sendAsset = b.NativeAsset() } else { sendAsset = b.CreditAsset(request.SendAssetCode, request.SendAssetIssuer) } payWith := b.PayWith(sendAsset, request.SendMax) for i := 0; ; i++ { codeFieldName := fmt.Sprintf("path[%d][asset_code]", i) issuerFieldName := fmt.Sprintf("path[%d][asset_issuer]", i) // If the element does not exist in PostForm break the loop if _, exists := r.PostForm[codeFieldName]; !exists { break } code := r.PostFormValue(codeFieldName) issuer := r.PostFormValue(issuerFieldName) if code == "" && issuer == "" { payWith = payWith.Through(b.NativeAsset()) } else { payWith = payWith.Through(b.CreditAsset(code, issuer)) } } payWithMutator = &payWith } var operationBuilder interface{} if request.AssetCode != "" && request.AssetIssuer != "" { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.CreditAmount{request.AssetCode, request.AssetIssuer, request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } operationBuilder = b.Payment(mutators...) } else { mutators := []interface{}{ b.Destination{destinationObject.AccountID}, b.NativeAmount{request.Amount}, } if payWithMutator != nil { mutators = append(mutators, *payWithMutator) } // Check if destination account exist _, err = rh.Horizon.LoadAccount(destinationObject.AccountID) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error loading account") operationBuilder = b.CreateAccount(mutators...) } else { operationBuilder = b.Payment(mutators...) } } memoType := request.MemoType memo := request.Memo if destinationObject.MemoType != "" { if request.MemoType != "" { log.Print("Memo given in request but federation returned memo fields.") server.Write(w, bridge.PaymentCannotUseMemo) return } memoType = destinationObject.MemoType memo = destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = &b.MemoText{memo} case memoType == "hash": memoBytes, err := hex.DecodeString(memo) if err != nil || len(memoBytes) != 32 { log.WithFields(log.Fields{"memo": memo}).Print("Cannot decode hash memo value") server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } var b32 [32]byte copy(b32[:], memoBytes[0:32]) hash := xdr.Hash(b32) memoMutator = &b.MemoHash{hash} default: log.Print("Not supported memo type: ", memoType) server.Write(w, protocols.NewInvalidParameterError("memo", request.Memo)) return } accountResponse, err := rh.Horizon.LoadAccount(sourceKeypair.Address()) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot load source account") server.Write(w, bridge.PaymentSourceNotExist) return } sequenceNumber, err := strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot convert SequenceNumber") server.Write(w, protocols.InternalServerError) return } transactionMutators := []b.TransactionMutator{ b.SourceAccount{request.Source}, b.Sequence{sequenceNumber + 1}, b.Network{rh.Config.NetworkPassphrase}, operationBuilder.(b.TransactionMutator), } if memoMutator != nil { transactionMutators = append(transactionMutators, memoMutator.(b.TransactionMutator)) } tx := b.Transaction(transactionMutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") // TODO when build.OperationBuilder interface is ready check for // create_account and payment errors separately switch { case tx.Err.Error() == "Asset code length is invalid": server.Write( w, protocols.NewInvalidParameterError("asset_code", request.AssetCode), ) case strings.Contains(tx.Err.Error(), "cannot parse amount"): server.Write( w, protocols.NewInvalidParameterError("amount", request.Amount), ) default: log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") server.Write(w, protocols.InternalServerError) } return } txe := tx.Sign(request.Source) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot encode transaction envelope") server.Write(w, protocols.InternalServerError) return } submitResponse, submitError = rh.Horizon.SubmitTransaction(txeB64) } if submitError != nil { log.WithFields(log.Fields{"error": submitError}).Error("Error submitting transaction") server.Write(w, protocols.InternalServerError) return } errorResponse := bridge.ErrorFromHorizonResponse(submitResponse) if errorResponse != nil { log.WithFields(errorResponse.LogData).Error(errorResponse.Error()) server.Write(w, errorResponse) return } // Path payment send amount if submitResponse.ResultXdr != nil { var transactionResult xdr.TransactionResult reader := strings.NewReader(*submitResponse.ResultXdr) b64r := base64.NewDecoder(base64.StdEncoding, reader) _, err := xdr.Unmarshal(b64r, &transactionResult) if err == nil && transactionResult.Result.Code == xdr.TransactionResultCodeTxSuccess { operationResult := (*transactionResult.Result.Results)[0] if operationResult.Tr.PathPaymentResult != nil { sendAmount := operationResult.Tr.PathPaymentResult.SendAmount() submitResponse.SendAmount = amount.String(sendAmount) } } } server.Write(w, &submitResponse) }
func (rh *RequestHandler) Authorize(w http.ResponseWriter, r *http.Request) { accountId := r.PostFormValue("account_id") assetCode := r.PostFormValue("asset_code") _, err := keypair.Parse(accountId) if err != nil { log.Print("Invalid accountId parameter: ", accountId) errorBadRequest(w, errorResponseString("invalid_account_id", "accountId parameter is invalid")) return } if !rh.isAssetAllowed(assetCode) { log.Print("Asset code not allowed: ", assetCode) errorBadRequest(w, errorResponseString("invalid_asset_code", "Given assetCode not allowed")) return } operationMutator := b.AllowTrust( b.Trustor{accountId}, b.Authorize{true}, b.AllowTrustAsset{assetCode}, ) submitResponse, err := rh.TransactionSubmitter.SubmitTransaction( *rh.Config.Accounts.AuthorizingSeed, operationMutator, nil, ) if err != nil { log.Print("Error submitting transaction ", err) errorServerError(w) return } if submitResponse.Errors != nil { var errorString string if submitResponse.Errors.OperationErrorCode != "" { switch submitResponse.Errors.OperationErrorCode { case "allow_trust_malformed": errorString = errorResponseString( "allow_trust_malformed", "Asset name is malformed.", ) case "allow_trust_not_trustline": errorString = errorResponseString( "allow_trust_not_trustline", "Trustor does not have a trustline yet.", ) case "allow_trust_trust_not_required": errorString = errorResponseString( "allow_trust_trust_not_required", "Authorizing account does not require allowing trust. Set AUTH_REQUIRED_FLAG on your account to use this feature.", ) case "allow_trust_trust_cant_revoke": errorString = errorResponseString( "allow_trust_trust_cant_revoke", "Authorizing account has AUTH_REVOCABLE_FLAG set. Can't revoke the trustline.", ) default: errorServerError(w) return } } else if submitResponse.Errors.TransactionErrorCode != "" { switch submitResponse.Errors.TransactionErrorCode { case "transaction_bad_seq": errorString = errorResponseString( "transaction_bad_seq", "Bad Sequence. Please, try again.", ) default: errorServerError(w) return } } errorBadRequest(w, errorString) return } json, err := json.MarshalIndent(submitResponse, "", " ") if err != nil { errorServerError(w) return } w.Write(json) }
func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { source := r.PostFormValue("source") sourceKeypair, err := keypair.Parse(source) if err != nil { log.WithFields(log.Fields{"source": source}).Print("Invalid source parameter") errorBadRequest(w, errorResponseString("invalid_source", "source parameter is invalid")) return } destination := r.PostFormValue("destination") destinationObject, err := rh.AddressResolver.Resolve(destination) if err != nil { log.WithFields(log.Fields{"destination": destination}).Print("Cannot resolve address") errorBadRequest(w, errorResponseString("invalid_destination", "Cannot resolve destination")) return } _, err = keypair.Parse(destinationObject.AccountId) if err != nil { log.WithFields(log.Fields{"AccountId": destinationObject.AccountId}).Print("Invalid AccountId in destination") errorBadRequest(w, errorResponseString("invalid_destination", "destination parameter is invalid")) return } amount := r.PostFormValue("amount") assetCode := r.PostFormValue("asset_code") assetIssuer := r.PostFormValue("asset_issuer") var operationBuilder interface{} if assetCode != "" && assetIssuer != "" { issuerKeypair, err := keypair.Parse(assetIssuer) if err != nil { log.WithFields(log.Fields{"asset_issuer": assetIssuer}).Print("Invalid asset_issuer parameter") errorBadRequest(w, errorResponseString("invalid_issuer", "asset_issuer parameter is invalid")) return } operationBuilder = b.Payment( b.Destination{destinationObject.AccountId}, b.CreditAmount{assetCode, issuerKeypair.Address(), amount}, ) } else if assetCode == "" && assetIssuer == "" { mutators := []interface{}{ b.Destination{destinationObject.AccountId}, b.NativeAmount{amount}, } // Check if destination account exist _, err = rh.Horizon.LoadAccount(destinationObject.AccountId) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error loading account") operationBuilder = b.CreateAccount(mutators...) } else { operationBuilder = b.Payment(mutators...) } } else { log.Print("Missing asset param.") errorBadRequest(w, errorResponseString("asset_missing_param", "When passing asser both params: `asset_code`, `asset_issuer` are required")) return } memoType := r.PostFormValue("memo_type") memo := r.PostFormValue("memo") if !(((memoType == "") && (memo == "")) || ((memoType != "") && (memo != ""))) { log.Print("Missing one of memo params.") errorBadRequest(w, errorResponseString("memo_missing_param", "When passing memo both params: `memo_type`, `memo` are required")) return } if destinationObject.MemoType != nil { if memoType != "" { log.Print("Memo given in request but federation returned memo fields.") errorBadRequest(w, errorResponseString("cannot_use_memo", "Memo given in request but federation returned memo fields")) return } memoType = *destinationObject.MemoType memo = *destinationObject.Memo } var memoMutator interface{} switch { case memoType == "": break case memoType == "id": id, err := strconv.ParseUint(memo, 10, 64) if err != nil { log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") errorBadRequest(w, errorResponseString("cannot_convert_memo_id", "Cannot convert memo_id value")) return } memoMutator = b.MemoID{id} case memoType == "text": memoMutator = &b.MemoText{memo} default: log.Print("Not supported memo type: ", memoType) errorBadRequest(w, errorResponseString("memo_not_supported", "Not supported memo type")) return } accountResponse, err := rh.Horizon.LoadAccount(sourceKeypair.Address()) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot load source account") errorBadRequest(w, errorResponseString("source_not_exist", "source account does not exist")) return } sequenceNumber, err := strconv.ParseUint(accountResponse.SequenceNumber, 10, 64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot convert SequenceNumber") errorServerError(w) return } transactionMutators := []b.TransactionMutator{ b.SourceAccount{source}, b.Sequence{sequenceNumber + 1}, b.Network{rh.Config.NetworkPassphrase}, operationBuilder.(b.TransactionMutator), } if memoMutator != nil { transactionMutators = append(transactionMutators, memoMutator.(b.TransactionMutator)) } tx := b.Transaction(transactionMutators...) if tx.Err != nil { log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") // TODO when build.OperationBuilder interface is ready check for // create_account and payment errors separately switch { case tx.Err.Error() == "Asset code length is invalid": errorBadRequest(w, errorResponseString("asset_code_invalid", "asset_code param is invalid")) case strings.Contains(tx.Err.Error(), "cannot parse amount"): errorBadRequest(w, errorResponseString("invalid_amount", "amount is invalid")) default: log.WithFields(log.Fields{"err": tx.Err}).Print("Transaction builder error") errorServerError(w) } return } txe := tx.Sign(source) txeB64, err := txe.Base64() if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot encode transaction envelope") errorServerError(w) return } submitResponse, err := rh.Horizon.SubmitTransaction(txeB64) if err != nil { log.WithFields(log.Fields{"error": err}).Error("Error submitting transaction") errorServerError(w) return } response, err := json.MarshalIndent(submitResponse, "", " ") if err != nil { log.WithFields(log.Fields{"error": err}).Error("Cannot Marshal submitResponse") errorServerError(w) return } if submitResponse.Ledger != nil { w.Write(response) } else { errorBadRequest(w, string(response)) } }
func (c *Config) Validate() (err error) { if c.Port == nil { err = errors.New("port param is required") return } if c.Horizon == nil { err = errors.New("horizon param is required") return } else { _, err = url.Parse(*c.Horizon) if err != nil { err = errors.New("Cannot parse horizon param") return } } if c.NetworkPassphrase == "" { err = errors.New("network_passphrase param is required") return } var dbUrl *url.URL dbUrl, err = url.Parse(c.Database.Url) if err != nil { err = errors.New("Cannot parse database.url param") return } switch c.Database.Type { case "mysql": // Add `parseTime=true` param to mysql url query := dbUrl.Query() query.Set("parseTime", "true") dbUrl.RawQuery = query.Encode() c.Database.Url = dbUrl.String() case "postgres": case "sqlite3": break default: err = errors.New("Invalid database.type param") return } if c.Accounts != nil { if c.Accounts.AuthorizingSeed != nil { _, err = keypair.Parse(*c.Accounts.AuthorizingSeed) if err != nil { err = errors.New("accounts.authorizing_seed is invalid") return } } if c.Accounts.IssuingSeed != nil { _, err = keypair.Parse(*c.Accounts.IssuingSeed) if err != nil { err = errors.New("accounts.issuing_seed is invalid") return } } if c.Accounts.ReceivingAccountId != nil { _, err = keypair.Parse(*c.Accounts.ReceivingAccountId) if err != nil { err = errors.New("accounts.receiving_account_id is invalid") return } } } if c.Hooks != nil { if c.Hooks.Receive != nil { _, err = url.Parse(*c.Hooks.Receive) if err != nil { err = errors.New("Cannot parse hooks.receive param") return } } if c.Hooks.Error != nil { _, err = url.Parse(*c.Hooks.Error) if err != nil { err = errors.New("Cannot parse hooks.error param") return } } } return }
// Validate validates config and returns error if any of config values is incorrect func (c *Config) Validate() (err error) { if c.ExternalPort == nil { err = errors.New("external_port param is required") return } if c.InternalPort == nil { err = errors.New("internal_port param is required") return } if c.NetworkPassphrase == "" { err = errors.New("network_passphrase param is required") return } if c.Keys.SigningSeed == "" || c.Keys.EncryptionKey == "" { err = errors.New("keys.signing_seed and keys.encryption_key params are required") return } if c.Keys.SigningSeed != "" { _, err = keypair.Parse(c.Keys.SigningSeed) if err != nil { err = errors.New("keys.signing_seed is invalid") return } } if c.Keys.EncryptionKey != "" { _, err = keypair.Parse(c.Keys.EncryptionKey) if err != nil { err = errors.New("keys.encryption_key is invalid") return } } var dbURL *url.URL dbURL, err = url.Parse(c.Database.URL) if err != nil { err = errors.New("Cannot parse database.url param") return } switch c.Database.Type { case "mysql": // Add `parseTime=true` param to mysql url query := dbURL.Query() query.Set("parseTime", "true") dbURL.RawQuery = query.Encode() c.Database.URL = dbURL.String() case "postgres": break default: err = errors.New("Invalid database.type param") return } if c.Callbacks.Sanctions != "" { _, err = url.Parse(c.Callbacks.Sanctions) if err != nil { err = errors.New("Cannot parse callbacks.sanctions param") return } } if c.Callbacks.Sanctions != "" { _, err = url.Parse(c.Callbacks.Sanctions) if err != nil { err = errors.New("Cannot parse callbacks.sanctions param") return } } if c.Callbacks.AskUser != "" { _, err = url.Parse(c.Callbacks.AskUser) if err != nil { err = errors.New("Cannot parse callbacks.ask_user param") return } } if c.Callbacks.FetchInfo != "" { _, err = url.Parse(c.Callbacks.FetchInfo) if err != nil { err = errors.New("Cannot parse callbacks.fetch_info param") return } } return }
func TestRequestHandlerSend(t *testing.T) { mockAddressResolverHelper := new(MockAddressResolverHelper) addressResolver := AddressResolver{mockAddressResolverHelper} mockTransactionSubmitter := new(mocks.MockTransactionSubmitter) IssuingSeed := "SC34WILLHVADXMP6ACPMIRA6TRAWJMVCLPFNW7S6MUMXJVLAZUC4EWHP" AuthorizingSeed := "SC37TBSIAYKIDQ6GTGLT2HSORLIHZQHBXVFI5P5K4Q5TSHRTRBK3UNWG" config := config.Config{ Assets: []string{"USD", "EUR"}, Accounts: &config.Accounts{ // GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR IssuingSeed: &IssuingSeed, // GBQXA3ABGQGTCLEVZIUTDRWWJOQD5LSAEDZAG7GMOGD2HBLWONGUVO4I AuthorizingSeed: &AuthorizingSeed, }, } issuingKeypair, err := keypair.Parse(*config.Accounts.IssuingSeed) if err != nil { panic(err) } requestHandler := RequestHandler{ AddressResolver: addressResolver, Config: &config, TransactionSubmitter: mockTransactionSubmitter, } testServer := httptest.NewServer(http.HandlerFunc(requestHandler.Send)) defer testServer.Close() Convey("Given send request", t, func() { Convey("When destination is invalid", func() { destination := "GD3YBOYIUVLU" assetCode := "USD" Convey("it should return error", func() { statusCode, response := getResponse(testServer, url.Values{"destination": {destination}, "asset_code": {assetCode}}) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) assert.Equal(t, errorResponseString("invalid_destination", "destination parameter is invalid"), responseString) }) }) Convey("When assetCode is invalid", func() { destination := "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632" assetCode := "GBP" Convey("it should return error", func() { statusCode, response := getResponse(testServer, url.Values{"destination": {destination}, "asset_code": {assetCode}}) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) assert.Equal(t, errorResponseString("invalid_asset_code", "Given assetCode not allowed"), responseString) }) }) Convey("When destination is a Stellar address", func() { params := url.Values{ "asset_code": {"USD"}, "amount": {"20"}, "destination": {"bob*stellar.org"}, } Convey("When stellar.toml does not exist", func() { mockAddressResolverHelper.On( "GetStellarToml", "stellar.org", ).Return( StellarToml{}, errors.New("stellar.toml response status code indicates error"), ).Once() Convey("it should return error", func() { statusCode, response := getResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) assert.Equal(t, errorResponseString("invalid_destination", "Cannot resolve destination"), responseString) }) }) Convey("When stellar.toml does not contain FEDERATION_SERVER", func() { mockAddressResolverHelper.On( "GetStellarToml", "stellar.org", ).Return( StellarToml{}, nil, ).Once() Convey("it should return error", func() { statusCode, response := getResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) assert.Equal(t, errorResponseString("invalid_destination", "Cannot resolve destination"), responseString) }) }) Convey("When GetDestination() errors", func() { federationServer := "http://api.example.com" mockAddressResolverHelper.On( "GetStellarToml", "stellar.org", ).Return( StellarToml{&federationServer}, nil, ).Once() mockAddressResolverHelper.On( "GetDestination", "http://api.example.com", "bob*stellar.org", ).Return( StellarDestination{}, errors.New("Only HTTPS federation servers allowed"), ).Once() Convey("it should return error", func() { statusCode, response := getResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 400, statusCode) assert.Equal(t, errorResponseString("invalid_destination", "Cannot resolve destination"), responseString) }) }) Convey("When federation response is correct", func() { federationServer := "http://api.example.com" mockAddressResolverHelper.On( "GetStellarToml", "stellar.org", ).Return( StellarToml{&federationServer}, nil, ).Once() mockAddressResolverHelper.On( "GetDestination", "http://api.example.com", "bob*stellar.org", ).Return(StellarDestination{AccountId: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, nil).Once() var ledger uint64 ledger = 1988728 expectedSubmitResponse := horizon.SubmitTransactionResponse{&ledger, nil, nil} operation := b.Payment( b.Destination{"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, b.CreditAmount{ params.Get("asset_code"), issuingKeypair.Address(), params.Get("amount"), }, ) mockTransactionSubmitter.On( "SubmitTransaction", *config.Accounts.IssuingSeed, operation, nil, ).Return(expectedSubmitResponse, nil).Once() Convey("it should return success", func() { statusCode, response := getResponse(testServer, params) responseString := strings.TrimSpace(string(response)) expectedResponse, err := json.MarshalIndent(expectedSubmitResponse, "", " ") if err != nil { panic(err) } assert.Equal(t, 200, statusCode) assert.Equal(t, string(expectedResponse), responseString) }) }) }) Convey("When destination is an accountId", func() { params := url.Values{ "asset_code": {"USD"}, "amount": {"20"}, "destination": {"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, } Convey("When params are valid", func() { operation := b.Payment( b.Destination{params.Get("destination")}, b.CreditAmount{ params.Get("asset_code"), issuingKeypair.Address(), params.Get("amount"), }, ) Convey("transaction fails", func() { mockTransactionSubmitter.On( "SubmitTransaction", *config.Accounts.IssuingSeed, operation, nil, ).Return( horizon.SubmitTransactionResponse{}, errors.New("Error sending transaction"), ).Once() Convey("it should return server error", func() { statusCode, response := getResponse(testServer, params) responseString := strings.TrimSpace(string(response)) assert.Equal(t, 500, statusCode) assert.Equal(t, getServerErrorResponseString(), responseString) mockTransactionSubmitter.AssertExpectations(t) }) }) Convey("transaction succeeds (no memo)", func() { var ledger uint64 ledger = 100 expectedSubmitResponse := horizon.SubmitTransactionResponse{ Ledger: &ledger, } mockTransactionSubmitter.On( "SubmitTransaction", *config.Accounts.IssuingSeed, operation, nil, ).Return(expectedSubmitResponse, nil).Once() Convey("it should succeed", func() { statusCode, response := getResponse(testServer, params) var actualSubmitTransactionResponse horizon.SubmitTransactionResponse json.Unmarshal(response, &actualSubmitTransactionResponse) assert.Equal(t, 200, statusCode) assert.Equal(t, expectedSubmitResponse, actualSubmitTransactionResponse) mockTransactionSubmitter.AssertExpectations(t) }) }) Convey("transaction succeeds (with memo)", func() { params.Add("memo_type", "id") params.Add("memo", "123") var ledger uint64 ledger = 100 expectedSubmitResponse := horizon.SubmitTransactionResponse{ Ledger: &ledger, } memo := b.MemoID{123} mockTransactionSubmitter.On( "SubmitTransaction", *config.Accounts.IssuingSeed, operation, memo, ).Return(expectedSubmitResponse, nil).Once() Convey("it should succeed", func() { statusCode, response := getResponse(testServer, params) var actualSubmitTransactionResponse horizon.SubmitTransactionResponse json.Unmarshal(response, &actualSubmitTransactionResponse) assert.Equal(t, 200, statusCode) assert.Equal(t, expectedSubmitResponse, actualSubmitTransactionResponse) mockTransactionSubmitter.AssertExpectations(t) }) }) }) }) }) }