예제 #1
0
// 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
}
예제 #2
0
// 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
}
예제 #3
0
// 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
}
예제 #4
0
// IsValidAccountID returns true if account ID is valid
func IsValidAccountID(accountID string) bool {
	_, err := keypair.Parse(accountID)
	if err != nil {
		return false
	}
	return true
}
예제 #5
0
func setAccountId(addressOrSeed string, aid *xdr.AccountId) error {
	kp, err := keypair.Parse(addressOrSeed)
	if err != nil {
		return err
	}

	return aid.SetAddress(kp.Address())
}
예제 #6
0
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())
}
예제 #7
0
// 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
}
예제 #8
0
// 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
}
예제 #9
0
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
}
예제 #10
0
// 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,
	}
}
예제 #11
0
// 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
}
예제 #12
0
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
}
예제 #13
0
// 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
}
예제 #14
0
// 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))
	}

}
예제 #19
0
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
}
예제 #20
0
// 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)
					})
				})
			})
		})
	})
}