Пример #1
0
func (x *XdrStream) ReadOne(in interface{}) error {
	var nbytes uint32
	err := binary.Read(x.rdr, binary.BigEndian, &nbytes)
	if err != nil {
		x.rdr.Close()
		if err == io.ErrUnexpectedEOF {
			return io.EOF
		} else {
			return err
		}
	}
	nbytes &= 0x7fffffff
	x.buf.Reset()
	if nbytes == 0 {
		x.rdr.Close()
		return io.EOF
	}
	x.buf.Grow(int(nbytes))
	read, err := x.buf.ReadFrom(io.LimitReader(x.rdr, int64(nbytes)))
	if read != int64(nbytes) {
		x.rdr.Close()
		return errors.New("Read wrong number of bytes from XDR")
	}
	if err != nil {
		x.rdr.Close()
		return err
	}

	readi, err := xdr.Unmarshal(&x.buf, in)
	if int64(readi) != int64(nbytes) {
		return fmt.Errorf("Unmarshalled %d bytes from XDR, expected %d)",
			readi, nbytes)
	}
	return err
}
Пример #2
0
func (ac CoreAccountRecord) DecodeThresholds() (xdr.Thresholds, error) {
	reader := strings.NewReader(ac.Thresholds)
	b64r := base64.NewDecoder(base64.StdEncoding, reader)
	var xdrThresholds xdr.Thresholds

	_, err := xdr.Unmarshal(b64r, &xdrThresholds)
	if err != nil {
		err = errors.Wrap(err, 1)
	}

	return xdrThresholds, err
}
Пример #3
0
func ExampleDecodeTransaction() {
	data := "AAAAAGL8HQvQkbK2HA3WVjRrKmjX00fG8sLI7m0ERwJW/AX3AAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAABVvwF9wAAAEAKZ7IPj/46PuWU6ZOtyMosctNAkXRNX9WCAI5RnfRk+AyxDLoDZP/9l3NvsxQtWj9juQOuoBlFLnWu8intgxQA"

	rawr := strings.NewReader(data)
	b64r := base64.NewDecoder(base64.StdEncoding, rawr)

	var tx xdr.TransactionEnvelope
	bytesRead, err := xdr.Unmarshal(b64r, &tx)

	fmt.Printf("read %d bytes\n", bytesRead)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("This tx has %d operations\n", len(tx.Tx.Operations))
	// Output: read 192 bytes
	// This tx has 1 operations
}
Пример #4
0
func ExampleDecodeTransaction() {
	data := "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAAByWDl7wAAAEAza1ouO/OTJDMMwUDewoqooFqHDulZ/nWFekNycPVCRtw70wZIN0UURhx8lYh1e911oahT3nBjAFZgwAwijY0O"

	rawr := strings.NewReader(data)
	b64r := base64.NewDecoder(base64.StdEncoding, rawr)

	var tx xdr.TransactionEnvelope
	bytesRead, err := xdr.Unmarshal(b64r, &tx)

	fmt.Printf("read %d bytes\n", bytesRead)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("This tx has %d operations\n", len(tx.Tx.Operations))
	// Output: read 192 bytes
	// This tx has 1 operations
}
Пример #5
0
func ExampleDecodeTransaction() {
	data := "rqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAKAAAAAwAAAAEAAAAAAAA" +
		"AAAAAAAEAAAAAAAAAAW5oJtVdnYOVdZqtXpTHBtbcY0mCmfcBIKEgWnlvFIhaAAAAAA" +
		"AAAAAC+vCAAAAAAa6jei0gQGmrUfm+o2CMv/w32YzJgGYlmgG6CUW3FwyD6AZ/5TtPZ" +
		"qEt9kyC3GJeXfzoS667ZPhPUSNjSWgAeDPHFLcM"

	rawr := strings.NewReader(data)
	b64r := base64.NewDecoder(base64.StdEncoding, rawr)

	var tx xdr.TransactionEnvelope
	bytesRead, err := xdr.Unmarshal(b64r, &tx)

	fmt.Printf("read %d bytes\n", bytesRead)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("This tx has %d operations\n", len(tx.Tx.Operations))
	// Output: read 180 bytes
	// This tx has 1 operations
}
Пример #6
0
func TestDecode(t *testing.T) {
	Convey("Decode XDR Enum", t, func() {
		// works for positive values
		var op xdr.OperationType
		r := bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x00})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeCreateAccount)

		r = bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x08})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeAccountMerge)

		// works for negative values
		var trc xdr.TransactionResultCode
		r = bytes.NewReader([]byte{0xFF, 0xFF, 0xFF, 0xFF})
		xdr.Unmarshal(r, &trc)
		So(trc, ShouldEqual, xdr.TransactionResultCodeTxFailed)

		r = bytes.NewReader([]byte{0x00, 0xFF, 0xFF, 0xFF})
		_, err := xdr.Unmarshal(r, &op)
		So(err, ShouldNotBeNil)
	})

	Convey("Decodes Memo", t, func() {
		data := "AAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var memo xdr.Memo

		_, err := xdr.Unmarshal(b64r, &memo)

		So(err, ShouldBeNil)
	})

	Convey("Decodes AccountID", t, func() {
		data := "AAAAAK6jei3jmoI8TGlD/egc37PXtHKKzWV8wViZBaCu5L5M"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var id xdr.AccountId
		_, err := xdr.Unmarshal(b64r, &id)

		So(err, ShouldBeNil)
	})

	Convey("Decodes OperationBody", t, func() {
		data := "AAAAAAAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAA7msoA"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var body xdr.OperationBody
		_, err := xdr.Unmarshal(b64r, &body)

		So(err, ShouldBeNil)
	})

	Convey("Decode TransactionResultPair", t, func() {
		data := "mf13Xm7tPjMcffhLVA2VXbTs6fV9IpgHFZGKy3zlu/QAAAAAAAAACgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var trp xdr.TransactionResultPair
		_, err := xdr.Unmarshal(b64r, &trp)

		So(err, ShouldBeNil)
		So(len(trp.TransactionHash), ShouldEqual, 32)
		So(trp.Result.FeeCharged, ShouldEqual, 10)

		trr := trp.Result.Result
		So(trr.Code, ShouldEqual, xdr.TransactionResultCodeTxSuccess)

		So(trr.Results, ShouldNotBeNil)

		r := trr.MustResults()
		So(len(r), ShouldEqual, 1)

		opr := r[0]
		So(opr.Code, ShouldEqual, xdr.OperationResultCodeOpInner)

		oprr := opr.MustTr()
		So(oprr.Type, ShouldEqual, xdr.OperationTypeCreateAccount)

		cr := oprr.MustCreateAccountResult()
		So(cr.Code, ShouldEqual, xdr.CreateAccountResultCodeCreateAccountSuccess)

		So(func() {
			oprr.MustAccountMergeResult()
		}, ShouldPanic)
	})

	Convey("Decode TransactionEnvelope", t, func() {
		data := "AAAAAGL8HQvQkbK2HA3WVjRrKmjX00fG8sLI7m0ERwJW/AX3AAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAABVvwF9wAAAEAKZ7IPj/46PuWU6ZOtyMosctNAkXRNX9WCAI5RnfRk+AyxDLoDZP/9l3NvsxQtWj9juQOuoBlFLnWu8intgxQA"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		_, err := xdr.Unmarshal(b64r, &txe)

		So(err, ShouldBeNil)
		So(len(txe.Signatures), ShouldEqual, 1)

		tx := txe.Tx

		So(tx.Fee, ShouldEqual, 10)
		So(tx.SeqNum, ShouldEqual, 1)

		So(tx.Memo.Type, ShouldEqual, xdr.MemoTypeMemoNone)

		op := tx.Operations[0]

		So(op.SourceAccount, ShouldBeNil)

		p := op.Body.MustCreateAccountOp()
		So(p.StartingBalance, ShouldEqual, 1000000000)
	})

	Convey("Decode TransactionMeta", t, func() {
		data := "AAAAAAAAAAEAAAABAAAAAgAAAAAAAAAAYvwdC9CRsrYcDdZWNGsqaNfTR8bywsjubQRHAlb8BfcBY0V4XYn/9gAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAgAAAAAAAAACAAAAAAAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAA7msoAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAAAAAABi/B0L0JGythwN1lY0aypo19NHxvLCyO5tBEcCVvwF9wFjRXgh7zX2AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var m xdr.TransactionMeta
		_, err := xdr.Unmarshal(b64r, &m)

		So(err, ShouldBeNil)
		op := m.MustOperations()
		So(len(op), ShouldEqual, 1)
		So(len(op[0].Changes), ShouldEqual, 1)
	})

	Convey("Roundtrip TransactionEnvelope", t, func() {
		data := "AAAAAGL8HQvQkbK2HA3WVjRrKmjX00fG8sLI7m0ERwJW/AX3AAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAABVvwF9wAAAEAKZ7IPj/46PuWU6ZOtyMosctNAkXRNX9WCAI5RnfRk+AyxDLoDZP/9l3NvsxQtWj9juQOuoBlFLnWu8intgxQA"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		n, err := xdr.Unmarshal(b64r, &txe)
		So(err, ShouldBeNil)

		var output bytes.Buffer
		b64w := base64.NewEncoder(base64.StdEncoding, &output)

		n2, err := xdr.Marshal(b64w, txe)

		So(n2, ShouldEqual, n)
		So(output.String(), ShouldEqual, data)
	})
}
Пример #7
0
// HandlerAuth implements authorize endpoint
func (rh *RequestHandler) HandlerAuth(c web.C, w http.ResponseWriter, r *http.Request) {
	authreq := &compliance.AuthRequest{}
	authreq.FromRequest(r)

	err := authreq.Validate()
	if err != nil {
		errorResponse := err.(*protocols.ErrorResponse)
		log.WithFields(errorResponse.LogData).Error(errorResponse.Error())
		server.Write(w, errorResponse)
		return
	}

	var authData compliance.AuthData
	err = json.Unmarshal([]byte(authreq.Data), &authData)
	if err != nil {
		errorResponse := protocols.NewInvalidParameterError("data", authreq.Data)
		log.WithFields(errorResponse.LogData).Warn(errorResponse.Error())
		server.Write(w, errorResponse)
		return
	}

	senderStellarToml, err := rh.StellarTomlResolver.GetStellarTomlByAddress(authData.Sender)
	if err != nil {
		log.WithFields(log.Fields{"err": err, "sender": authData.Sender}).Warn("Cannot get stellar.toml of sender")
		server.Write(w, protocols.InvalidParameterError)
		return
	}

	if senderStellarToml.SigningKey == "" {
		errorResponse := protocols.NewInvalidParameterError("data.sender", authData.Sender)
		log.WithFields(errorResponse.LogData).Warn("No SIGNING_KEY in stellar.toml of sender")
		server.Write(w, errorResponse)
		return
	}

	// Verify signature
	signatureBytes, err := base64.StdEncoding.DecodeString(authreq.Signature)
	if err != nil {
		errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature)
		log.WithFields(errorResponse.LogData).Warn("Error decoding signature")
		server.Write(w, errorResponse)
		return
	}
	err = rh.SignatureSignerVerifier.Verify(senderStellarToml.SigningKey, []byte(authreq.Data), signatureBytes)
	if err != nil {
		log.WithFields(log.Fields{
			"signing_key": senderStellarToml.SigningKey,
			"data":        authreq.Data,
			"sig":         authreq.Signature,
		}).Warn("Invalid signature")
		errorResponse := protocols.NewInvalidParameterError("sig", authreq.Signature)
		server.Write(w, errorResponse)
		return
	}

	b64r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(authData.Tx))
	var tx xdr.Transaction
	_, err = xdr.Unmarshal(b64r, &tx)
	if err != nil {
		errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx)
		log.WithFields(log.Fields{
			"err": err,
			"tx":  authData.Tx,
		}).Warn("Error decoding Transaction XDR")
		server.Write(w, errorResponse)
		return
	}

	if tx.Memo.Hash == nil {
		errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx)
		log.WithFields(log.Fields{"tx": authData.Tx}).Warn("Transaction does not contain Memo.Hash")
		server.Write(w, errorResponse)
		return
	}

	// Validate memo preimage hash
	memoPreimageHashBytes := sha256.Sum256([]byte(authData.Memo))
	memoBytes := [32]byte(*tx.Memo.Hash)

	if memoPreimageHashBytes != memoBytes {
		errorResponse := protocols.NewInvalidParameterError("data.tx", authData.Tx)

		h := xdr.Hash(memoPreimageHashBytes)
		tx.Memo.Hash = &h

		var txBytes bytes.Buffer
		_, err = xdr.Marshal(&txBytes, tx)
		if err != nil {
			log.Error("Error mashaling transaction")
			server.Write(w, protocols.InternalServerError)
			return
		}

		expectedTx := base64.StdEncoding.EncodeToString(txBytes.Bytes())

		log.WithFields(log.Fields{"tx": authData.Tx, "expected_tx": expectedTx}).Warn("Memo preimage hash does not equal tx Memo.Hash")
		server.Write(w, errorResponse)
		return
	}

	var memoPreimage memo.Memo
	err = json.Unmarshal([]byte(authData.Memo), &memoPreimage)
	if err != nil {
		errorResponse := protocols.NewInvalidParameterError("data.memo", authData.Memo)
		log.WithFields(log.Fields{
			"err":  err,
			"memo": authData.Memo,
		}).Warn("Cannot unmarshal memo preimage")
		server.Write(w, errorResponse)
		return
	}

	transactionHash, err := submitter.TransactionHash(&tx, rh.Config.NetworkPassphrase)
	if err != nil {
		log.WithFields(log.Fields{"err": err}).Warn("Error calculating tx hash")
		server.Write(w, protocols.InternalServerError)
		return
	}

	response := compliance.AuthResponse{}

	// Sanctions check
	if rh.Config.Callbacks.Sanctions == "" {
		response.TxStatus = compliance.AuthStatusOk
	} else {
		resp, err := rh.Client.PostForm(
			rh.Config.Callbacks.Sanctions,
			url.Values{"sender": {memoPreimage.Transaction.SenderInfo}},
		)
		if err != nil {
			log.WithFields(log.Fields{
				"sanctions": rh.Config.Callbacks.Sanctions,
				"err":       err,
			}).Error("Error sending request to sanctions server")
			server.Write(w, protocols.InternalServerError)
			return
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Error("Error reading sanctions server response")
			server.Write(w, protocols.InternalServerError)
			return
		}

		switch resp.StatusCode {
		case http.StatusOK: // AuthStatusOk
			response.TxStatus = compliance.AuthStatusOk
		case http.StatusAccepted: // AuthStatusPending
			response.TxStatus = compliance.AuthStatusPending

			var pendingResponse compliance.PendingResponse
			err := json.Unmarshal(body, &pendingResponse)
			if err != nil {
				// Set default value
				response.Pending = 600
			} else {
				response.Pending = pendingResponse.Pending
			}
		case http.StatusForbidden: // AuthStatusDenied
			response.TxStatus = compliance.AuthStatusDenied
		default:
			log.WithFields(log.Fields{
				"status": resp.StatusCode,
				"body":   string(body),
			}).Error("Error response from sanctions server")
			server.Write(w, protocols.InternalServerError)
			return
		}
	}

	// User info
	if authData.NeedInfo {
		if rh.Config.Callbacks.AskUser == "" {
			response.InfoStatus = compliance.AuthStatusDenied

			// Check AllowedFi
			tokens := strings.Split(authData.Sender, "*")
			if len(tokens) != 2 {
				log.WithFields(log.Fields{
					"sender": authData.Sender,
				}).Warn("Invalid stellar address")
				server.Write(w, protocols.InternalServerError)
				return
			}

			allowedFi, err := rh.Repository.GetAllowedFiByDomain(tokens[1])
			if err != nil {
				log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedFi from DB")
				server.Write(w, protocols.InternalServerError)
				return
			}

			if allowedFi == nil {
				// FI not found check AllowedUser
				allowedUser, err := rh.Repository.GetAllowedUserByDomainAndUserID(tokens[1], tokens[0])
				if err != nil {
					log.WithFields(log.Fields{"err": err}).Error("Error getting AllowedUser from DB")
					server.Write(w, protocols.InternalServerError)
					return
				}

				if allowedUser != nil {
					response.InfoStatus = compliance.AuthStatusOk
				}
			} else {
				response.InfoStatus = compliance.AuthStatusOk
			}
		} else {
			// Ask user
			var amount, assetType, assetCode, assetIssuer string

			if len(tx.Operations) > 0 {
				operationBody := tx.Operations[0].Body
				if operationBody.Type == xdr.OperationTypePayment {
					amount = baseAmount.String(operationBody.PaymentOp.Amount)
					operationBody.PaymentOp.Asset.Extract(&assetType, &assetCode, &assetIssuer)
				} else if operationBody.Type == xdr.OperationTypePathPayment {
					amount = baseAmount.String(operationBody.PathPaymentOp.DestAmount)
					operationBody.PathPaymentOp.DestAsset.Extract(&assetType, &assetCode, &assetIssuer)
				}
			}

			resp, err := rh.Client.PostForm(
				rh.Config.Callbacks.AskUser,
				url.Values{
					"amount":       {amount},
					"asset_code":   {assetCode},
					"asset_issuer": {assetIssuer},
					"sender":       {memoPreimage.Transaction.SenderInfo},
					"note":         {memoPreimage.Transaction.Note},
				},
			)
			if err != nil {
				log.WithFields(log.Fields{
					"ask_user": rh.Config.Callbacks.AskUser,
					"err":      err,
				}).Error("Error sending request to ask_user server")
				server.Write(w, protocols.InternalServerError)
				return
			}

			defer resp.Body.Close()
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				log.Error("Error reading ask_user server response")
				server.Write(w, protocols.InternalServerError)
				return
			}

			switch resp.StatusCode {
			case http.StatusOK: // AuthStatusOk
				response.InfoStatus = compliance.AuthStatusOk
			case http.StatusAccepted: // AuthStatusPending
				response.InfoStatus = compliance.AuthStatusPending

				var pendingResponse compliance.PendingResponse
				err := json.Unmarshal(body, &pendingResponse)
				if err != nil {
					// Set default value
					response.Pending = 600
				} else {
					response.Pending = pendingResponse.Pending
				}
			case http.StatusForbidden: // AuthStatusDenied
				response.InfoStatus = compliance.AuthStatusDenied
			default:
				log.WithFields(log.Fields{
					"status": resp.StatusCode,
					"body":   string(body),
				}).Error("Error response from ask_user server")
				server.Write(w, protocols.InternalServerError)
				return
			}
		}

		if response.InfoStatus == compliance.AuthStatusOk {
			// Fetch Info
			fetchInfoRequest := compliance.FetchInfoRequest{Address: memoPreimage.Transaction.Route}
			resp, err := rh.Client.PostForm(
				rh.Config.Callbacks.FetchInfo,
				fetchInfoRequest.ToValues(),
			)
			if err != nil {
				log.WithFields(log.Fields{
					"fetch_info": rh.Config.Callbacks.FetchInfo,
					"err":        err,
				}).Error("Error sending request to fetch_info server")
				server.Write(w, protocols.InternalServerError)
				return
			}

			defer resp.Body.Close()
			body, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				log.WithFields(log.Fields{
					"fetch_info": rh.Config.Callbacks.FetchInfo,
					"err":        err,
				}).Error("Error reading fetch_info server response")
				server.Write(w, protocols.InternalServerError)
				return
			}

			if resp.StatusCode != http.StatusOK {
				log.WithFields(log.Fields{
					"fetch_info": rh.Config.Callbacks.FetchInfo,
					"status":     resp.StatusCode,
					"body":       string(body),
				}).Error("Error response from fetch_info server")
				server.Write(w, protocols.InternalServerError)
				return
			}

			response.DestInfo = string(body)
		}
	} else {
		response.InfoStatus = compliance.AuthStatusOk
	}

	if response.TxStatus == compliance.AuthStatusOk && response.InfoStatus == compliance.AuthStatusOk {
		authorizedTransaction := &entities.AuthorizedTransaction{
			TransactionID:  hex.EncodeToString(transactionHash[:]),
			Memo:           base64.StdEncoding.EncodeToString(memoBytes[:]),
			TransactionXdr: authData.Tx,
			AuthorizedAt:   time.Now(),
			Data:           authreq.Data,
		}
		err = rh.EntityManager.Persist(authorizedTransaction)
		if err != nil {
			log.WithFields(log.Fields{"err": err}).Warn("Error persisting AuthorizedTransaction")
			server.Write(w, protocols.InternalServerError)
			return
		}
	}

	server.Write(w, &response)
}
Пример #8
0
func unmarshalTransactionResult(transactionResult string) (txResult xdr.TransactionResult, err error) {
	reader := strings.NewReader(transactionResult)
	b64r := base64.NewDecoder(base64.StdEncoding, reader)
	_, err = xdr.Unmarshal(b64r, &txResult)
	return
}
Пример #9
0
func TestDecode(t *testing.T) {
	Convey("Decode XDR Enum", t, func() {
		// works for positive values
		var op xdr.OperationType
		r := bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x00})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeCreateAccount)

		r = bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x08})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeAccountMerge)

		// works for negative values
		var trc xdr.TransactionResultCode
		r = bytes.NewReader([]byte{0xFF, 0xFF, 0xFF, 0xFF})
		xdr.Unmarshal(r, &trc)
		So(trc, ShouldEqual, xdr.TransactionResultCodeTxFailed)

		r = bytes.NewReader([]byte{0x00, 0xFF, 0xFF, 0xFF})
		_, err := xdr.Unmarshal(r, &op)
		So(err, ShouldNotBeNil)
	})

	Convey("Decode TransactionResultPair", t, func() {
		data := "W9EizvB5Q+UMEAJR9w3y+/xvR14aO27zXb/yoQod9L8AAAAAAAAACgAAAAAAAAABAAAAAAAAAAEAAAAA"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var trp xdr.TransactionResultPair
		_, err := xdr.Unmarshal(b64r, &trp)

		So(err, ShouldBeNil)
		So(len(trp.TransactionHash), ShouldEqual, 32)
		So(trp.Result.FeeCharged, ShouldEqual, 10)

		trr := trp.Result.Result
		So(trr.Code, ShouldEqual, xdr.TransactionResultCodeTxSuccess)

		So(trr.Results, ShouldNotBeNil)

		r := trr.MustResults()
		So(len(r), ShouldEqual, 1)

		opr := r[0]
		So(opr.Code, ShouldEqual, xdr.OperationResultCodeOpInner)

		oprr := opr.MustTr()
		So(oprr.Type, ShouldEqual, xdr.OperationTypePayment)

		pr := oprr.MustPaymentResult()
		So(pr.Code, ShouldEqual, xdr.PaymentResultCodePaymentSuccess)

		So(func() {
			oprr.MustAccountMergeResult()
		}, ShouldPanic)
	})

	Convey("Decode TransactionEnvelope", t, func() {
		data := "rqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAKAAAAAwAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAW5oJtVdnYOVdZqtXpTHBtbcY0mCmfcBIKEgWnlvFIhaAAAAAAAAAAAC+vCAAAAAAa6jei0gQGmrUfm+o2CMv/w32YzJgGYlmgG6CUW3FwyD6AZ/5TtPZqEt9kyC3GJeXfzoS667ZPhPUSNjSWgAeDPHFLcM"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		_, err := xdr.Unmarshal(b64r, &txe)

		So(err, ShouldBeNil)
		So(len(txe.Signatures), ShouldEqual, 1)

		tx := txe.Tx

		So(tx.Fee, ShouldEqual, 10)
		So(tx.SeqNum, ShouldEqual, 12884901889)

		So(tx.Memo.Type, ShouldEqual, xdr.MemoTypeMemoNone)

		op := tx.Operations[0]

		So(op.SourceAccount, ShouldBeNil)

		p := op.Body.MustPaymentOp()
		So(p.Currency.Type, ShouldEqual, xdr.CurrencyTypeCurrencyTypeNative)
		So(p.Amount, ShouldEqual, 50000000)
	})

	Convey("Decode TransactionMeta", t, func() {
		data := "AAAAAgAAAAEAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAPpW6gAAAAAMAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAQAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAA4n9l2AAAAAwAAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAA="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var m xdr.TransactionMeta
		_, err := xdr.Unmarshal(b64r, &m)

		So(err, ShouldBeNil)
		So(len(m.Changes), ShouldEqual, 2)
		m.Changes[0].MustUpdated()
		m.Changes[1].MustUpdated()
	})

	Convey("Roundtrip TransactionEnvelope", t, func() {
		data := "rqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAKAAAAAwAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAW5oJtVdnYOVdZqtXpTHBtbcY0mCmfcBIKEgWnlvFIhaAAAAAAAAAAAC+vCAAAAAAa6jei0gQGmrUfm+o2CMv/w32YzJgGYlmgG6CUW3FwyD6AZ/5TtPZqEt9kyC3GJeXfzoS667ZPhPUSNjSWgAeDPHFLcM"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		n, err := xdr.Unmarshal(b64r, &txe)
		So(err, ShouldBeNil)

		var output bytes.Buffer
		b64w := base64.NewEncoder(base64.StdEncoding, &output)

		n2, err := xdr.Marshal(b64w, txe)

		So(n2, ShouldEqual, n)
		So(output.String(), ShouldEqual, data)
	})
}
// 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)
}
Пример #11
0
func TestXdrDecode(t *testing.T) {

	xdrbytes := []byte{

		0, 0, 0, 0, // entry type 0, liveentry

		0, 32, 223, 100, // lastmodified 2154340

		0, 0, 0, 0, // entry type 0, account

		0, 0, 0, 0, // key type 0
		23, 140, 68, 253, // ed25519 key (32 bytes)
		184, 162, 186, 195,
		118, 239, 158, 210,
		100, 241, 174, 254,
		108, 110, 165, 140,
		75, 76, 83, 141,
		104, 212, 227, 80,
		1, 214, 157, 7,

		0, 0, 0, 29, // 64bit balance: 125339976000
		46, 216, 65, 64,

		0, 0, 129, 170, // 64bit seqnum: 142567144423475
		0, 0, 0, 51,

		0, 0, 0, 1, // numsubentries: 1

		0, 0, 0, 1, // inflationdest type, populated

		0, 0, 0, 0, // key type 0
		87, 240, 19, 71, // ed25519 key (32 bytes)
		52, 91, 9, 62,
		213, 239, 178, 85,
		161, 119, 108, 251,
		168, 90, 76, 116,
		12, 48, 134, 248,
		115, 255, 117, 50,
		19, 18, 170, 203,

		0, 0, 0, 0, // flags

		0, 0, 0, 19, // homedomain: 19 bytes + 1 null padding
		99, 101, 110, 116, // "centaurus.xcoins.de"
		97, 117, 114, 117,
		115, 46, 120, 99,
		111, 105, 110, 115,
		46, 100, 101, 0,

		1, 0, 0, 0, // thresholds
		0, 0, 0, 0, // signers (null)

		0, 0, 0, 0, // entry.account.ext.v: 0

		0, 0, 0, 0, // entry.ext.v: 0
	}

	assert.Equal(t, len(xdrbytes), 152)

	var tmp xdr.BucketEntry
	n, err := xdr.Unmarshal(bytes.NewReader(xdrbytes[:]), &tmp)
	fmt.Printf("Decoded %d bytes\n", n)
	if err != nil {
		panic(err)
	}
	assert.Equal(t, len(xdrbytes), n)

	var out bytes.Buffer
	n, err = xdr.Marshal(&out, &tmp)
	fmt.Printf("Encoded %d bytes\n", n)
	if err != nil {
		panic(err)
	}

	assert.Equal(t, out.Len(), n)
	assert.Equal(t, out.Bytes(), xdrbytes)
}
Пример #12
0
func TestDecode(t *testing.T) {
	Convey("Decode XDR Enum", t, func() {
		// works for positive values
		var op xdr.OperationType
		r := bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x00})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeCreateAccount)

		r = bytes.NewReader([]byte{0x00, 0x00, 0x00, 0x08})
		xdr.Unmarshal(r, &op)
		So(op, ShouldEqual, xdr.OperationTypeAccountMerge)

		// works for negative values
		var trc xdr.TransactionResultCode
		r = bytes.NewReader([]byte{0xFF, 0xFF, 0xFF, 0xFF})
		xdr.Unmarshal(r, &trc)
		So(trc, ShouldEqual, xdr.TransactionResultCodeTxFailed)

		r = bytes.NewReader([]byte{0x00, 0xFF, 0xFF, 0xFF})
		_, err := xdr.Unmarshal(r, &op)
		So(err, ShouldNotBeNil)
	})

	Convey("Decodes Memo", t, func() {
		data := "AAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var memo xdr.Memo

		_, err := xdr.Unmarshal(b64r, &memo)

		So(err, ShouldBeNil)
	})

	Convey("Decodes AccountID", t, func() {
		data := "AAAAAK6jei3jmoI8TGlD/egc37PXtHKKzWV8wViZBaCu5L5M"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var id xdr.AccountId
		_, err := xdr.Unmarshal(b64r, &id)

		So(err, ShouldBeNil)
	})

	Convey("Decodes OperationBody", t, func() {
		data := "AAAAAAAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAA7msoA"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var body xdr.OperationBody
		_, err := xdr.Unmarshal(b64r, &body)

		So(err, ShouldBeNil)
	})

	Convey("Decode TransactionResultPair", t, func() {
		data := "mf13Xm7tPjMcffhLVA2VXbTs6fV9IpgHFZGKy3zlu/QAAAAAAAAACgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var trp xdr.TransactionResultPair
		_, err := xdr.Unmarshal(b64r, &trp)

		So(err, ShouldBeNil)
		So(len(trp.TransactionHash), ShouldEqual, 32)
		So(trp.Result.FeeCharged, ShouldEqual, 10)

		trr := trp.Result.Result
		So(trr.Code, ShouldEqual, xdr.TransactionResultCodeTxSuccess)

		So(trr.Results, ShouldNotBeNil)

		r := trr.MustResults()
		So(len(r), ShouldEqual, 1)

		opr := r[0]
		So(opr.Code, ShouldEqual, xdr.OperationResultCodeOpInner)

		oprr := opr.MustTr()
		So(oprr.Type, ShouldEqual, xdr.OperationTypeCreateAccount)

		cr := oprr.MustCreateAccountResult()
		So(cr.Code, ShouldEqual, xdr.CreateAccountResultCodeCreateAccountSuccess)

		So(func() {
			oprr.MustAccountMergeResult()
		}, ShouldPanic)
	})

	Convey("Decode TransactionEnvelope", t, func() {
		data := "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAAByWDl7wAAAEAza1ouO/OTJDMMwUDewoqooFqHDulZ/nWFekNycPVCRtw70wZIN0UURhx8lYh1e911oahT3nBjAFZgwAwijY0O"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		_, err := xdr.Unmarshal(b64r, &txe)

		So(err, ShouldBeNil)
		So(len(txe.Signatures), ShouldEqual, 1)

		tx := txe.Tx

		So(tx.Fee, ShouldEqual, 10)
		So(tx.SeqNum, ShouldEqual, 1)

		So(tx.Memo.Type, ShouldEqual, xdr.MemoTypeMemoNone)

		op := tx.Operations[0]

		So(op.SourceAccount, ShouldBeNil)

		p := op.Body.MustCreateAccountOp()
		So(p.StartingBalance, ShouldEqual, 1000000000)
	})

	Convey("Decode TransactionMeta", t, func() {
		data := "AAAAAAAAAAEAAAABAAAAAAAAAACJmyhA7VY2xW3cXxSyOXX3nxuiOI0mlOTFbs3dyWDl7wFjRXhdif/2AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAQAAAAIAAAAAAAAAAAAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAA7msoAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAiZsoQO1WNsVt3F8Usjl1958bojiNJpTkxW7N3clg5e8BY0V4Ie819gAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAA=="
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var m xdr.TransactionMeta
		_, err := xdr.Unmarshal(b64r, &m)

		So(err, ShouldBeNil)
		tm := m.MustV0()
		So(len(tm.Changes), ShouldEqual, 1)
		So(len(tm.Operations), ShouldEqual, 1)
		So(len(tm.Operations[0].Changes), ShouldEqual, 2)
	})

	Convey("Roundtrip TransactionEnvelope", t, func() {
		data := "AAAAAImbKEDtVjbFbdxfFLI5dfefG6I4jSaU5MVuzd3JYOXvAAAACgAAAAAAAAABAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAArqN6LeOagjxMaUP96Bzfs9e0corNZXzBWJkFoK7kvkwAAAAAO5rKAAAAAAAAAAAByWDl7wAAAEAza1ouO/OTJDMMwUDewoqooFqHDulZ/nWFekNycPVCRtw70wZIN0UURhx8lYh1e911oahT3nBjAFZgwAwijY0O"
		rawr := strings.NewReader(data)
		b64r := base64.NewDecoder(base64.StdEncoding, rawr)

		var txe xdr.TransactionEnvelope
		n, err := xdr.Unmarshal(b64r, &txe)
		So(err, ShouldBeNil)

		var output bytes.Buffer
		b64w := base64.NewEncoder(base64.StdEncoding, &output)

		n2, err := xdr.Marshal(b64w, txe)

		So(n2, ShouldEqual, n)
		So(output.String(), ShouldEqual, data)
	})
}