diff --git a/go.list b/go.list index 5cbd88f118..c9db790501 100644 --- a/go.list +++ b/go.list @@ -20,11 +20,6 @@ github.com/eapache/go-resiliency v1.1.0 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 github.com/eapache/queue v1.1.0 github.com/elazarl/go-bindata-assetfs v1.0.0 -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 -github.com/facebookgo/inject v0.0.0-20161006174721-cc1aa653e50f -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 -github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 github.com/fatih/structs v1.0.0 github.com/fsnotify/fsnotify v1.4.7 github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 @@ -37,7 +32,6 @@ github.com/go-sql-driver/mysql v1.4.0 github.com/go-stack/stack v1.8.0 github.com/gobuffalo/packr v1.12.1 github.com/gogo/protobuf v1.2.0 -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/mock v1.1.1 github.com/golang/protobuf v1.3.1 diff --git a/go.mod b/go.mod index f1eed25e95..a0ab5b3240 100644 --- a/go.mod +++ b/go.mod @@ -12,18 +12,12 @@ require ( github.com/aws/aws-sdk-go v1.25.25 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/elazarl/go-bindata-assetfs v1.0.0 - github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect - github.com/facebookgo/inject v0.0.0-20161006174721-cc1aa653e50f - github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect - github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect - github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect github.com/fatih/structs v1.0.0 // indirect github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 // indirect github.com/getsentry/raven-go v0.0.0-20160805001729-c9d3cc542ad1 github.com/go-chi/chi v4.0.3+incompatible github.com/go-errors/errors v0.0.0-20150906023321-a41850380601 github.com/gobuffalo/packr v1.12.1 // indirect - github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 // indirect github.com/google/martian v2.1.0+incompatible // indirect github.com/googleapis/gax-go v2.0.2+incompatible // indirect diff --git a/go.sum b/go.sum index 84213c5c3d..f345dbe777 100644 --- a/go.sum +++ b/go.sum @@ -33,16 +33,6 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= -github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= -github.com/facebookgo/inject v0.0.0-20161006174721-cc1aa653e50f h1:jK9r9Ofgc/Yzdlod77G23LfYtwqAmkQCZ9MaP6779OI= -github.com/facebookgo/inject v0.0.0-20161006174721-cc1aa653e50f/go.mod h1:oO8UHw+fDHjDsk4CTy/E96WDzFUYozAtBAaGNoVL0+c= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= -github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 h1:KnnwHN59Jxec0htA2pe/i0/WI9vxXLQifdhBrP3lqcQ= -github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691/go.mod h1:sKLL1iua/0etWfo/nPCmyz+v2XDMXy+Ho53W7RAuZNY= -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= -github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -64,8 +54,6 @@ github.com/gobuffalo/packr v1.12.1 h1:+5u3rqgdhswdYXhrX6DHaO7BM4P8oxrbvgZm9H1cRI github.com/gobuffalo/packr v1.12.1/go.mod h1:H2dZhQFqHeZwr/5A/uGQkBp7xYuMGuzXFeKhYdcz5No= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/services/bridge/CHANGELOG.md b/services/bridge/CHANGELOG.md deleted file mode 100644 index c7ca03a665..0000000000 --- a/services/bridge/CHANGELOG.md +++ /dev/null @@ -1,204 +0,0 @@ -# Changelog - -As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log. - -## Unreleased - -* Dropped support for Go 1.12. - -## 0.0.33 - -* Add `ReadTimeout` to HTTP server configuration to fix potential DoS vector. -* Fixed path-payment operation in `/payment` -* Dropped support for Go 1.10, 1.11. - -## 0.0.32 - -* Bridge server now uses the new Go SDK. -* Unit tests added. - -## Breaking changes - -* MySQL is no longer supported. To migrate your data to postgresql use any of the tools provided [here](https://wiki.postgresql.org/wiki/Converting_from_other_Databases_to_PostgreSQL#MySQL). - -* Requests to `/builder` for `AllowTrust` operations must specify the source account for the operation. - - -## Changes -* Payload MAC authentication uses `X-Payload-Mac` header (old `X_PAYLOAD_MAC` header is still provided for backward compatibility, but it is deprecated and will be removed in future versions). - -## 0.0.31 - -### Breaking changes -* `id` parameter is now required when sending payments using Compliance Protocol. - -### Changes -* `nonce` value does not change when repeating Auth Request after receiving `pending` status. -* Support for sending XLM using Compliance Protocol. -* Fix for #109 - -Please migrate your `compliance` DB before running a new version using: `compliance --migrate-db`. - -## 0.0.30 - -* Support for "Forward federation" destinations. - -## 0.0.29 - -* Improved transaction submission code: - * High rate transaction submission using `/payment` endpoint should work better. - * Added `id` parameter to `/payment` request: payments with `id` set, when resubmitted, are using previously created transaction envelope stored in a DB instead of recreating a transaction with a new sequence number. This can prevent accidental double-spends. -* Fix for a bug in `/builder` endpoint: sequence number is now incremented when loaded from Horizon server (https://github.com/stellar/bridge-server/issues/86). -* Payment listener is now also sending `account_merge` operations and, for each operation, a new parameter: `transaction_id`. -* Updated `github.com/BurntSushi/toml` dependency. - -Read `README` file for more information about new features. - -Please migrate your `bridge` DB before running a new version using: `bridge --migrate-db` - -## 0.0.28 - -* Added error messages to Compliance protocol ([SEP-0003](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0003.md)) - -## 0.0.27 - -* Admin Panel (`/admin` endpoint in `bridge` server). -* `/tx_status` endpoint [More info](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md). -* Sequence number in automatically loaded if it's not set in `/builder`. -* Fixed log levels in `PaymentListener` (#73). -* Fixed `AllowedFI` table name under Windows (#72). -* New `-v` parameter to print app version. - -## 0.0.26 - -* Fix log level in `PaymentListener`. - -## 0.0.25 - -* [XLM (lumen)](https://www.stellar.org/lumens/) payments can be now used in `PaymentListener`. -* Fixed a loop in `PaymentListener` occurring when multiple payments fail. - -## 0.0.24 - -* Better responses. -* Use `http.Client` with `Timeout`. - -## 0.0.23 - -* Fix a bug in `protocols.Asset.String`. Add more_info field to `invalid_parameter` errors. - -## 0.0.22 - -* Ability to reprocess received payments. - -## 0.0.21 - -* Add asset issuer to receive callback - -## 0.0.20 - -* Update `github.com/stellar/go` dependency. - -## 0.0.19 - -* Fix account ID destinations in /payment - -## 0.0.18 - -* Fixed `-config` param in bridge. - -## 0.0.17 - -* Added `-config` param to use custom config file -* Removed unused `EncryptionKey` config param -* Added use_compliance parameter in `/payment` to force using compliance protocol. - -## 0.0.16 - -* New version of compliance protocol. - -## 0.0.15 - -* Change stellar.toml location to new standard. - -## 0.0.14 - -* Bug fixes for postgres - -## 0.0.13 - -* Add `mac_key` configuration - -## 0.0.12 - -* Fix `inject` in compliance server. - -## 0.0.11 - -* `/create-keypair` endpoint, -* Sending routing information in receive callback. - -## 0.0.10 - -* Send only relevant data to compliance callbacks (#17). -* `hooks` are now called `callbacks` in `bridge` server. - -## 0.0.9 - -* Transaction builder (#14) - -## 0.0.8 - -* [Compliance protocol](https://www.stellar.org/developers/guides/compliance-protocol.html) support. -* Saving and reading memo preimage. -* This repo will now contain two apps: `bridge` (for building, submitting and monitoring transactions) and `compliance` (for Compliance protocol). Both are built in a single build process. Each app has it's own README file. -* Dependency injection is now done using [facebookgo/inject](https://godoc.org/github.com/facebookgo/inject). -* Handling and validation of requests and responses is now done in `protocols` package. This package contains methods for transforming `url.Values` from/to request structs and for marshalling responses. It also contains common errors (missing/invalid fields, internal server error, etc.) and all protocol-specific error responses. It also includes stellar.toml and federation resolving. -* New `net` and `server` packages that contain some helper network connected functions and structs. -* Improvements to `db` package. - -## 0.0.7 - -* Add path payments, -* Change `config.toml` file structure, -* Partial implementation of Compliance Protocol. - -## 0.0.6 - -* When there are no `ReceivePayment`s in database, payment listener will start with cursor `now`. -* Fix a bug in `db.Repository.GetLastCursorValue()`. - -## 0.0.5 - -* Add `MEMO_HASH` support. - -## 0.0.4 - -* Fixed bugs connected with running server using `postgres` DB (full refactoring of `db` package), -* Fixed starting a minimum server with a single endpoint: `/payment`. - -## 0.0.3 - -* Send `create_account` operation in `/payment` if account does not exist. -* Fixed major bug in `PaymentListener`. -* Sending to Stellar address with memo in `/send`. -* Standardized responses. -* Updated README file. - -## 0.0.2 - -* Added `/payment` endpoint. -* Now it's possible to start a server with parameter that are not required. Minimum version starts a server with a single endpoint: `/payment`. -* Added config parameters validation. -* Added `network_passphrase` config parameter. -* `postgres` migration files. -* Fixed sending to Stellar address. -* Fixed `horizon.AccountResponse.SequenceNumber` bug. -* Fixed minor bugs. -* Code refactoring. -* Added example config file to the release package -* Updated README file. - -## 0.0.1 - -* Initial release. diff --git a/services/bridge/README.md b/services/bridge/README.md deleted file mode 100644 index 7479dfd164..0000000000 --- a/services/bridge/README.md +++ /dev/null @@ -1,479 +0,0 @@ -# bridge-server -This is a stand alone server written in go. It is designed to make connecting to the Stellar network as easy as possible. -It allows you to be notified when a payment is received by a particular account. It also allows you to send a payment via a HTTP request. -It can optionally be connected to a `compliance` server if you want to carry out the compliance protocol. -It can be used by any project that needs to accept or send payments such as anchors or merchants accepting payments. - -Handles: - -- Creating Stellar transactions. -- Monitoring a receiving Stellar account. - - -## Downloading the server -[Prebuilt binaries](https://github.com/stellar/go/releases) of the bridge server are available on the [releases page](https://github.com/stellar/go/releases). - -| Platform | Binary file name | -|----------------|------------------------------------------------------------------------------------------| -| Mac OSX 64 bit | [bridge-vX.X.X-darwin-amd64](https://github.com/stellar/go/releases) | -| Linux 64 bit | [bridge-vX.X.X-linux-amd64](https://github.com/stellar/go/releases) | -| Windows 64 bit | [bridge-vX.X.X-windows-amd64.exe](https://github.com/stellar/go/releases) | - -Alternatively, you can [build](#building) the binary yourself. - -## Config - -The `bridge.cfg` file must be present in a working directory (you can load another file by using `-c` parameter). Here is an [example configuration file](https://github.com/stellar/go/blob/master/services/bridge/bridge_example.cfg). Config file should contain following values: - -* `port` - server listening port -* `api_key` - when set, all requests to bridge server must contain `api_key` parameter with a correct value, otherwise the server will respond with `503 Forbidden` -* `network_passphrase` - passphrase of the network that will be used with this bridge server: - * test network: `Test SDF Network ; September 2015` - * public network: `Public Global Stellar Network ; September 2015` -* `compliance` - URL to compliance server instance if you want to carry out the compliance protocol -* `horizon` - URL to [horizon](https://github.com/stellar/horizon) server instance -* `assets` - array of approved assets codes that this server can authorize or receive. These are currency code/issuer pairs. Use asset code 'XLM' with no issuer to listen for XLM payments. See [`bridge_example.cfg`](./bridge_example.cfg) for example. -* `database` - * `type` - database type (postgres) - * `url` - url to database connection: - * for `postgres`: `postgres://user:password@host/dbname?sslmode=sslmode` ([more info](https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters)) -* `accounts` - * `base_seed` - The secret seed of the account used to send payments. If left blank you will need to pass it in calls to `/payment`. - * `authorizing_seed` - The secret seed of the public key that is able to submit `allow_trust` operations on the issuing account. - * `issuing_account_id` - The account ID of the issuing account (only if you want to authorize trustlines via bridge server, otherwise leave empty). - * `receiving_account_id` - The account ID that receives incoming payments. The `callbacks.receive` will be called when a payment is received by this account. -* `callbacks` - * `receive` - URL of the webhook where requests will be sent when a new payment is sent to the receiving account. The bridge server will keep calling the receive callback indefinitely until 200 OK status is returned by it. **WARNING** The bridge server can send multiple requests to this webhook for a single payment! You need to be prepared for it. See: [Security](#security). - * `error` - URL of the webhook where requests will be sent when there is an error with an incoming payment -* `log_format` - set to `json` for JSON logs -* `mac_key` - a stellar secret key used to add MAC headers to a payment notification. - -Check [`bridge_example.cfg`](./bridge_example.cfg). - -The minimal set of config values contains: -* `port` -* `network_passphrase` -* `horizon` - -It will start a server with a single endpoint: `/payment`. - -## Getting started - -After creating `bridge.cfg` file, you need to run DB migrations: -``` -./bridge --migrate-db -``` - -Then you can start the server: -``` -./bridge -``` - -## API - -`Content-Type` of requests data should be `application/x-www-form-urlencoded`. - -### POST /create-keypair - -Creates a new random key pair. - -#### Response - -```json -{ - "public_key": "GCSLLOYK7IKDQKUDSSAPHSJT3Y5XLIDIAFPVO5K42IN5CAQPNHIHJ2DE", - "private_key": "SCJAOTWONWSOQLILCHNSGUOIXWCMIJQ563SPHMG25OPFX3IUDBAFU4SV" -} -``` - -In case of error it will return the following error: -* [`InternalServerError`](/src/github.com/stellar/gateway/protocols/errors.go) - -### POST /builder - -Builds a transaction from a given request. `Content-Type` of this request should be `application/json`. Check [List of operations](https://www.stellar.org/developers/learn/concepts/list-of-operations.html) doc to learn more about how each operation looks like. - -**Note** This will not submit a transaction to the network. Please use [Horizon](https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html) to submit a transaction. - -#### Request - -Check example request below (remove comments before submitting it to the `bridge` server): - -```json -{ - // Transaction source account - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - // Sequence number - "sequence_number": "123", - // List of operations in this transaction - "operations": [ - // First operation - { - // Operation type - "type": "create_account", - // Operation body - "body": { - // Don't send source field if operation source account is equal to transaction source account - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "destination": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "starting_balance": "50" - } - }, - // Second operation - { - "type": "payment", - "body": { - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "destination": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "amount": "1050", - "asset": { - "code": "USD", - "issuer": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT" - } - } - }, - { - "type": "path_payment", - "body": { - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "destination": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "destination_amount": "10", - "send_max": "1050", - "send_asset": { - "code": "USD", - "issuer": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT" - }, - "destination_asset": { - "code": "EUR", - "issuer": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT" - }, - "path": [ - {}, // Native asset - { - "code": "ZAR", - "issuer": "GBNIVKJTD2SMAXB5ALPBZ7CHRYYLCO5XSH55H6TI3Z37P7SCRXQVESG2" - } - ] - } - }, - { - "type": "manage_offer", - "body": { - "selling": { - "code": "EUR", - "issuer": "GDOJMKTDLGGLROSSM5BV5MXIAQ3JZHASQFUV55WBJ45AFOUXSVVFGPTJ" - }, - "buying": { - "code": "USD", - "issuer": "GACETOPHMOLSZLG5IQ3D6KQDKCAAYUYTTQHIEY6IGZE4VOBDD2YY6YAO" - }, - "amount": "123456", - "price": "2.93850088", - "offer_id": "100" - } - }, - { - "type": "create_passive_offer", - "body": { - "selling": { - "code": "EUR", - "issuer": "GDOJMKTDLGGLROSSM5BV5MXIAQ3JZHASQFUV55WBJ45AFOUXSVVFGPTJ" - }, - "buying": { - "code": "USD", - "issuer": "GACETOPHMOLSZLG5IQ3D6KQDKCAAYUYTTQHIEY6IGZE4VOBDD2YY6YAO" - }, - "amount": "123456", - "price": "2.93850088" - } - }, - { - "type": "set_options", - "body": { - "inflation_dest": "GBMPZVOMJ67WQBTBCVURDKTGL4557272EGQMAJCXPSMLOE63XPLL6SVA", - "set_flags": [1, 2], - "clear_flags": [4], - "master_weight": 100, - "low_threshold": 1, - "medium_threshold": 2, - "high_threshold": 3, - "home_domain": "stellar.org", - "signer": { - "public_key": "GA6VMJJQM2QBPPIXK2UVTAOS4XSSSAKSCOGFQE55IMRBQR65GIVDTTQV", - "weight": 5 - } - } - }, - { - "type": "change_trust", - "body": { - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "asset": { - "code": "USD", - "issuer": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT" - } - } - }, - { - "type": "allow_trust", - "body": { - "source": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "trustor": "GBDCOZD7CHY26KS6ABEZPIJAMS2G7GP3YSTJ6DIRIQ6YUU77ZAPI2LVT", - "asset_code": "USD", - "authorize": true - } - }, - { - "type": "account_merge", - "body": { - "destination": "GBLH67TQHRNRLERQEIQJDNBV2DSWPHAPP43MBIF7DVKA7X55APUNS4LL" - } - }, - { - "type": "inflation", - "body": {} - }, - { - "type": "manage_data", - "body": { - "name": "test_data", - "data": "AQIDBAUG" - } - } - ], - // Array of signers - "signers": ["SDOTALIMPAM2IV65IOZA7KZL7XWZI5BODFXTRVLIHLQZQCKK57PH5F3H"] -} -``` - -Assets are represented by a JSON object with two fields: `code` and `issuer`. Empty JSON object represents [native asset](https://www.stellar.org/developers/learn/concepts/assets.html#lumens-xlm). - -#### Response - -When transaction can be successfully built it will return a JSON object with a single `transaction_envelope` field that will contain base64-encoded `TransactionEnvelope` XDR object: - -```json -{ - "transaction_envelope": "AAAAAEYnZH8R8a8qXgBJl6EgZLRvmfvEpp8NEUQ9i..." -} -``` - -In case of error it will return one of the following errors: -* [`InternalServerError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`InvalidParameterError`](/src/github.com/stellar/gateway/protocols/errors.go) - -### POST /payment - -Builds and submits a transaction with a single [`payment`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#payment), [`path_payment`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#path-payment) or [`create_account`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#create-account) (when sending native asset to account that does not exist) operation built from following parameters. - -#### Safe transaction resubmittion - -It’s possible that you will not receive a response from Bridge server due to a bug, network conditions, etc. In such situation it’s impossible to determine the status of your transaction and sending the same request to the Bridge server may result in "double-spend" of the funds. That’s why you should always send a request with `id` parameter set. Then when you resubmit a transaction with the same `id` the previously used [sequence number](https://www.stellar.org/developers/guides/concepts/transactions.html) will be reused. - -If the transaction has already been successfully applied to the ledger, Horizon server will simply return the saved result and not attempt to submit the transaction again. Only in cases where a transaction’s status is unknown (and thus will have a chance of being included into a ledger) will a resubmission to the network occur. - -#### Request Parameters - -Every request must contain required parameters from the following list. Additionally, depending on a type of payment, every request must contain required parameters for equivalent operation type. - -name | | description ---- | --- | --- -`id` | optional | Unique ID of the payment. If you send another request with the same `id` previously sent transaction will be resubmitted to the network. This parameter is required when sending a payment using Compliance protocol. -`source` | optional | Secret seed of transaction source account. If ommitted it will use the `base_seed` specified in the config file. -`sender` | optional | Payment address (ex. `bob*stellar.org`) of payment sender account. Required for when sending using Compliance protocol. -`destination` | required | Account ID or payment address (ex. `bob*stellar.org`) of payment destination account -`forward_destination[domain]` | required | Required when sending to Forward destination. -`forward_destination[fields][name]` | required | Required when sending to Forward destination. Fields will be added to Federation request query string. -`amount` | required | Amount that destination will receive -`memo_type` | optional | Memo type, one of: `id`, `text`, `hash`, `extra` -`memo` | optional | Memo value, `id` it must be uint64, when `hash` it must be 32 bytes hex value. -`use_compliance` | optional | When `true` Bridge will use Compliance protocol even if `extra_memo` is empty. -`extra_memo` | optional | You can include any info here and it will be included in the pre-image of the transaction's memo hash. See the [Stellar Memo Convention](https://github.com/stellar/stellar-protocol/issues/28). When set and compliance server is connected, `memo` and `memo_type` values will be ignored. -`asset_code` | optional | Asset code (XLM when empty) destination will receive -`asset_issuer` | optional | Account ID of asset issuer (XLM when empty) destination will receive -`send_max` | optional | [path_payment] Maximum amount of send_asset to send -`send_asset_code` | optional | [path_payment] Sending asset code (XLM when empty) -`send_asset_issuer` | optional | [path_payment] Account ID of sending asset issuer (XLM when empty) -`path[n][asset_code]` | optional | [path_payment] If the path isn't specified the bridge server will find the path for you. Asset code of `n`th asset on the path (XLM when empty, but empty parameter must be sent!) -`path[n][asset_issuer]` | optional | [path_payment] Account ID of `n`th asset issuer (XLM when empty, but empty parameter must be sent!) -`path[n+1][asset_code]` | optional | [path_payment] Asset code of `n+1`th asset on the path (XLM when empty, but empty parameter must be sent!) -`path[n+1][asset_issuer]` | optional | [path_payment] Account ID of `n+1`th asset issuer (XLM when empty, but empty parameter must be sent!) -... | ... | _Up to 5 assets in the path..._ - -##### Forward destination example - -The following request to `/payment`: - -``` -forward_destination[domain]=stellar.org&forward_destination[fields][forward_type]=bank_account&forward_destination[fields][swift]=BOPBPHMM&forward_destination[fields][acct]=2382376 -``` - -will be translate to the following request: - -``` -https://FEDERATION_SERVER_READ_FROM_STELLAR_TOML/federation?type=forward&forward_type=bank_account&swift=BOPBPHMM&acct=2382376 -``` - -#### Response - -It will return [`SubmitTransactionResponse`](/src/github.com/stellar/gateway/horizon/submit_transaction_response.go) if there were no errors or with one of the following errors: - -* [`InternalServerError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`InvalidParameterError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`MissingParameterError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`TransactionBadSequence`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionBadAuth`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionInsufficientBalance`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionNoAccount`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionInsufficientFee`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionBadAuthExtra`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`PaymentCannotResolveDestination`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentCannotUseMemo`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentSourceNotExist`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentAssetCodeNotAllowed`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentPending`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentDenied`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentMalformed`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentUnderfunded`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentSrcNoTrust`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentSrcNotAuthorized`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentNoDestination`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentNoTrust`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentNotAuthorized`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentLineFull`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentNoIssuer`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentTooFewOffers`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentOfferCrossSelf`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) -* [`PaymentOverSendmax`](/src/github.com/stellar/gateway/protocols/bridge/payment.go) - -#### Example - -```sh -curl -X POST -d \ -"source=SBNDIK4N7ZM3ZJKDJJDWDSPSRPHNI2RFL36WNNNEGQEW3G3AH6VJ2QB7&\ -amount=1&\ -destination=GBIUXI4S27PSL6TTJCJMPYDCF3K6AW2MYORFRTC7QBFE6NNEGVOQK46H&\ -asset_code=USD&\ -asset_issuer=GASZUHRFAFIZX5LR4WNHBWUXJBZNBEWCHFTR4XZHPF5TMVM5XUZBP5DT&\ -memo_type=id&\ -memo=125" \ -http://localhost:8001/payment -``` - -### POST /authorize -Can be used to authorize other accounts to hold your assets. -It will build and submits a transaction with a [`allow_trust`](https://www.stellar.org/developers/learn/concepts/list-of-operations.html#allow-trust) operation. -The source of this transaction will be the account specified by `accounts.authorizing_seed` config parameter. -You should make sure that this account is a low weight signer on the issuing account. See [Multi-sig](https://www.stellar.org/developers/learn/concepts/multi-sig.html) for more information. - -#### Request Parameters - -name | | description ---- | --- | --- -`account_id` | required | Account ID of the account to authorize -`asset_code` | required | Asset code of the asset to authorize. Must be present in `assets` config array. - -#### Response - -It will return [`SubmitTransactionResponse`](/src/github.com/stellar/gateway/horizon/submit_transaction_response.go) if there were no errors or with one of the following errors: - -* [`InternalServerError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`InvalidParameterError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`MissingParameterError`](/src/github.com/stellar/gateway/protocols/errors.go) -* [`TransactionBadSequence`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionBadAuth`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionInsufficientBalance`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionNoAccount`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionInsufficientFee`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`TransactionBadAuthExtra`](/src/github.com/stellar/gateway/protocols/bridge/errors.go) -* [`AllowTrustMalformed`](/src/github.com/stellar/gateway/protocols/bridge/authorize.go) -* [`AllowTrustNoTrustline`](/src/github.com/stellar/gateway/protocols/bridge/authorize.go) -* [`AllowTrustTrustNotRequired`](/src/github.com/stellar/gateway/protocols/bridge/authorize.go) -* [`AllowTrustCantRevoke`](/src/github.com/stellar/gateway/protocols/bridge/authorize.go) - -### POST /reprocess -Can be used to reprocess received payment. - -#### Request Parameters - -name | | description ---- | --- | --- -`operation_id` | required | Horizon ID of operation to reprocess -`force` | optional | Must be set to `true` when reprocessing successful operations. - -## Callbacks - -The Bridge server listens for payment operations to the account specified by `accounts.receiving_account_id`. Every time -a payment arrives it will send a HTTP POST request to `callbacks.receive`. - -`Content-Type` of requests data will be `application/x-www-form-urlencoded`. - -### `callbacks.receive` - -The POST request with following parameters will be sent to this callback when a payment arrives. - -> **Warning!** This callback can be called multiple times. Please check `id` parameter and respond with `200 OK` in case of duplicate payment. - -#### Request - -name | description ---- | --- -`id` | Operation ID (ex. `23110707918671873`) -`from` | Account ID of the sender -`route` | The recipient ID at the receiving FI. This will be the routing information contained in the memo or memo value if no compliance server is connected or memo type is not `hash`. -`amount` | Amount that was sent -`asset_code` | Code of the asset sent (ex. `USD`) -`asset_issuer` | Issuer of the asset sent (ex. `GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR`) -`memo_type` | Type of the memo attached to the transaction. This field will be empty when no memo was attached. -`memo` | Value of the memo attached. This field will be empty when no memo was attached. -`data` | Value of the [AuthData](https://www.stellar.org/developers/guides/compliance-protocol.html). This field will be empty when compliance server is not connected. -`transaction_id` | The transaction hash of the operation (ex. `c7597583ad4f7caef15ad19b0f84017466b69790ee91bcacbbf98b51c93b17bf`) - -#### Response - -Respond with `200 OK` when processing succeeded. Any other status code will be considered an error and bridge server will keep sending this payment request again and will not continue to next payments until it receives `200 OK` response. - -#### Payload Authentication - -When the `mac_key` configuration value is set, the bridge server will attach HTTP headers to each payment notification that allow the receiver to verify that the notification is not forged. A header named `X-Payload-Mac` that contains a base64-encoded MAC value will be included. This MAC is derived by calculating the HMAC-SHA256 of the raw request body using the decoded value of the `mac_key` configuration option as the key. - -This MAC can be used on the receiving side of the notification to verify that the payment notifications was generated from the bridge server, rather than from some other actor, to increase security. - -## Security - -* This server must be set up in an isolated environment (ex. AWS VPC). Please make sure your firewall is properly configured -and accepts connections from a trusted IPs only. You can set the `api_key` config parameter as an additional protection but it's not recommended as the solely protection. -If you don't set this properly, an unauthorized person will be able to submit transactions from your accounts! -* Make sure the `callbacks` you provide only accept connections from the bridge server IP. -* Remember that `callbacks.receive` may be called multiple times with the same payment. Check `id` parameter and ignore -requests with the same value (just send `200 OK` response). - -## Building - -[gb](http://getgb.io) is used for building and testing. - -Given you have a running golang installation, you can build the server with: - -``` -gb build -``` - -After a successful build, you should find `bin/bridge` in the project directory. - -## Running tests - -``` -gb test -``` - -## Documentation - -``` -godoc -goroot=. -http=:6060 -``` - -Then simply open: -``` -http://localhost:6060/pkg/github.com/stellar/gateway/ -``` -in a browser. diff --git a/services/bridge/bridge_example.cfg b/services/bridge/bridge_example.cfg deleted file mode 100644 index b24ecb07ca..0000000000 --- a/services/bridge/bridge_example.cfg +++ /dev/null @@ -1,31 +0,0 @@ -# Bridge server bridge.cfg example - -port = 8006 -horizon = "https://horizon-testnet.stellar.org" -network_passphrase = "Test SDF Network ; September 2015" -api_key = "" -mac_key = "" - -[[assets]] -code="USD" -issuer="GCOGCYU77DLEVYCXDQM7F32M5PCKES6VU3Z5GURF6U6OA5LFOVTRYPOX" - -[[assets]] -code="EUR" -issuer="GCOGCYU77DLEVYCXDQM7F32M5PCKES6VU3Z5GURF6U6OA5LFOVTRYPOX" - -#Listen for XLM Payments -[[assets]] -code="XLM" - -[database] -type = "postgres" -url = "postgres://root@localhost/bridge?sslmode=disable" - -[accounts] -authorizing_seed = "SDMRITVCFY6IIK6H5DXIVUOL342YFVE3VFOGVF3D7XXHGITPX4ABMYXR" # GCAW3TYUYGCNODKO4QKMD6PSH5GP3KES4GWGVFCKZ6DD6EJUDUQ77BO -receiving_account_id = "GAJBUSUTGTS3MAU2KP6MWJFJACDN4ZJ5YCET23U6XYZZ7WUD2OYQQUR2" - -[callbacks] -receive = "http://localhost:8002/receive" -error = "http://localhost:8002/error" diff --git a/services/bridge/internal/config/main.go b/services/bridge/internal/config/main.go deleted file mode 100644 index 538feb3dac..0000000000 --- a/services/bridge/internal/config/main.go +++ /dev/null @@ -1,164 +0,0 @@ -package config - -import ( - "errors" - "net/url" - "regexp" - - "github.com/stellar/go/keypair" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" -) - -// Config contains config params of the bridge server -type Config struct { - Port *int `valid:"required"` - Horizon string `valid:"optional"` - Compliance string `valid:"optional"` - LogFormat string `valid:"optional" toml:"log_format"` - MACKey string `valid:"optional" toml:"mac_key"` - APIKey string `valid:"optional" toml:"api_key"` - NetworkPassphrase string `valid:"optional" toml:"network_passphrase"` - Develop bool `valid:"optional"` - Assets []protocols.Asset `valid:"optional"` - Database *Database `valid:"optional"` - Accounts Accounts `valid:"optional" toml:"accounts"` - Callbacks Callbacks `valid:"optional" toml:"callbacks"` -} - -// Accounts contains values of `accounts` config group -type Accounts struct { - AuthorizingSeed string `valid:"optional" toml:"authorizing_seed"` - BaseSeed string `valid:"optional" toml:"base_seed"` - IssuingAccountID string `valid:"optional" toml:"issuing_account_id"` - ReceivingAccountID string `valid:"optional" toml:"receiving_account_id"` -} - -// Callbacks contains values of `callbacks` config group -type Callbacks struct { - Receive string `valid:"optional"` - Error string `valid:"optional"` -} - -// Database contains values of `database` config group -type Database struct { - Type string `valid:"required"` - URL string `valid:"required"` -} - -var assetCodeMatch = regexp.MustCompile("^[a-zA-Z0-9]{1,12}$") - -// 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 - } - - for _, asset := range c.Assets { - if asset.Issuer == "" { - if asset.Code != "XLM" { - err = errors.New("Issuer param is required for " + asset.Code) - return - } - } - - if asset.Issuer != "" { - _, err = keypair.Parse(asset.Issuer) - if err != nil { - err = errors.New("Issuing account is invalid for " + asset.Code) - return - } - } - - matched := assetCodeMatch.MatchString(asset.Code) - if !matched { - return errors.New("Invalid asset code: " + asset.Code) - } - } - - _, err = url.Parse(c.Database.URL) - if err != nil { - err = errors.New("Cannot parse database.url param") - return - } - - switch c.Database.Type { - case "mysql": - err = errors.New("Invalid database.type param, mysql support is discontinued") - return - 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 -} diff --git a/services/bridge/internal/config/main_test.go b/services/bridge/internal/config/main_test.go deleted file mode 100644 index ca1188b346..0000000000 --- a/services/bridge/internal/config/main_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package config - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestConfig_Validate_db_type(t *testing.T) { - c := Config{ - Port: func(i int) *int { return &i }(8001), - Horizon: "https://example.com", - NetworkPassphrase: "Test SDF Network ; September 2015", - Database: &Database{ - Type: "", - URL: "", - }, - } - - testCases := []struct { - dbType string - wantErr error - }{ - {dbType: "", wantErr: nil}, - {dbType: "postgres", wantErr: nil}, - {dbType: "mysql", wantErr: errors.New("Invalid database.type param, mysql support is discontinued")}, - {dbType: "bogus", wantErr: errors.New("Invalid database.type param")}, - } - - for _, tc := range testCases { - t.Run(tc.dbType, func(t *testing.T) { - c.Database.Type = tc.dbType - err := c.Validate() - if tc.wantErr == nil { - assert.Nil(t, err) - } else { - require.NotNil(t, err) - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } - }) - } -} - -func TestConfig_Validate_db_url(t *testing.T) { - c := Config{ - Port: func(i int) *int { return &i }(8001), - Horizon: "https://example.com", - NetworkPassphrase: "Test SDF Network ; September 2015", - Database: &Database{ - Type: "", - URL: "", - }, - } - - testCases := []struct { - url string - wantErr error - }{ - {url: "", wantErr: nil}, - {url: "postgres://localhost/db", wantErr: nil}, - {url: " postgres:", wantErr: errors.New("Cannot parse database.url param")}, - } - - for _, tc := range testCases { - t.Run(tc.url, func(t *testing.T) { - c.Database.URL = tc.url - err := c.Validate() - if tc.wantErr == nil { - assert.Nil(t, err) - } else { - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } - }) - } -} diff --git a/services/bridge/internal/db/bindata.go b/services/bridge/internal/db/bindata.go deleted file mode 100644 index 3dc4ed2dd5..0000000000 --- a/services/bridge/internal/db/bindata.go +++ /dev/null @@ -1,365 +0,0 @@ -// Code generated by go-bindata. DO NOT EDIT. -// sources: -// latest.sql (5.393kB) -// migrations/01_init.sql (651B) -// migrations/02_payment_id.sql (235B) -// migrations/03_transaction_id.sql (156B) -// migrations/04_table_names.sql (257B) - -package db - -import ( - "bytes" - "compress/gzip" - "crypto/sha256" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _latestSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\xdb\x6e\xe2\x48\x13\xbe\xfe\x79\x8a\xba\x23\xd1\x6f\x18\xc8\x42\x26\x03\xca\x85\x07\x1c\x0d\x5a\x30\x19\x30\x3b\x13\x69\x25\xab\xb1\x0b\xa7\x15\xbb\xed\x74\xb7\x73\xd8\xa7\x5f\xb5\x0d\xc1\x27\x0e\x49\xd8\x4b\x77\x57\xd7\xe9\xab\xfa\xaa\xa0\xd1\xa8\x35\x1a\x70\x1b\x0a\xe9\x71\x9c\xff\x1c\x83\x4b\x24\x59\x12\x81\xe0\xc6\x41\x54\x6b\x34\x6a\xea\x7e\x18\x07\x11\xba\xb0\xe2\x61\xb0\x15\x78\x42\x2e\x68\xc8\xe0\x5b\xf3\xb2\xd9\xce\x48\x2d\x5f\x21\xf2\x6c\xf5\xbc\x20\x52\x9b\x1b\x16\x08\x49\x24\x06\xc8\xa4\x2d\x69\x80\x61\x2c\xe1\x1a\x5a\xfd\xe4\xca\x0f\x9d\x87\xf2\x29\x75\x7d\xb4\x29\xb3\x25\x27\x4c\x10\x47\xd2\x90\xd9\x02\x85\xd2\x5b\x16\x76\x7c\xaa\x54\x23\x73\x42\x97\x32\x0f\xae\xa1\xbe\xb0\x6e\xae\xea\xfd\x8d\x6d\xe6\x12\xee\xda\x4e\xc8\x56\x21\x0f\x28\xf3\x6c\x21\x39\x65\x9e\x80\x6b\x08\xd9\x5a\xc7\x3d\x3a\x0f\xf6\x2a\x66\xa9\xad\x65\xe8\x52\x54\xf7\x2b\xe2\x0b\xcc\x99\x09\x28\xb3\x03\x14\x82\x78\x89\xc0\x33\xe1\x8c\x32\x2f\x15\xe1\xe1\xb3\x2d\xd0\x89\x39\x95\xaf\x4a\xf9\x6a\xd5\x57\xa9\x54\x79\x32\x49\x80\x3d\x88\xfc\xc8\x13\x8f\x7e\x1f\xac\xd7\x08\x7b\x60\xfc\xb6\x0c\x73\x3e\x9a\x9a\x7d\x98\x3b\xf7\x18\x90\x1e\x34\xfa\x30\x7d\x66\xc8\x7b\x90\xe0\x30\x98\x19\xba\x65\x6c\x05\x61\x74\x03\xe6\xd4\x02\xe3\xf7\x68\x6e\xcd\x37\xfa\xe0\xd7\xc8\xfa\x01\xf3\xc1\x0f\x63\xa2\x2b\x1c\x1c\x22\x89\x1f\x7a\xfd\x5a\xde\xfa\x56\x4b\xc1\x8f\xc1\x74\x32\x31\x4c\x6b\xb7\x17\xe9\x3d\x4c\xcd\xb2\x0e\x18\xcd\xa1\x7e\x3b\xfe\x12\x79\xaa\x92\x22\x1e\x3a\xe8\xc6\x9c\xf8\xe0\x13\xe6\xc5\xc4\xc3\xba\x72\x23\x41\x02\x09\x77\xee\xed\x88\xc8\x7b\xb8\x86\x28\x5e\xfa\xd4\xd1\xf2\xee\x2a\x31\x17\x57\x24\xf6\xa5\x2d\xc9\xd2\x47\x11\x11\x07\x15\xa2\xf5\xc2\xed\x33\x95\xf7\x76\x48\xdd\x0c\x48\xb9\x58\xbd\x90\x47\x76\x40\x3d\x4e\x14\xa0\x62\x13\xa9\xa5\x7f\x1f\x1b\xdb\x38\x53\x27\xde\x82\x5d\x12\x2e\xf1\x21\x9b\xf8\x44\xbe\xa8\x0c\xce\x6a\x00\x00\xd4\x05\x89\x2f\x32\xc1\xc3\x5c\x8c\xc7\x5a\x72\x4a\xa2\xc8\xa7\xe8\xda\x44\x82\xaa\x54\x21\x49\x10\x81\xf2\x36\xf9\x84\x7f\x42\x86\xb5\x73\x95\x12\x7d\x6c\x19\xb3\x1d\x06\xa6\xbf\x4c\x75\x37\x5d\x7b\x54\x88\x8d\xa3\x83\xf4\x09\x5d\x3b\x22\xaf\xaa\xab\x3e\x17\x5c\x51\xdb\x36\xba\x25\xf5\x28\x2b\xc6\x17\x46\x98\x7a\x69\x53\x17\x9c\x7b\xc2\x89\x23\x91\xc3\x13\xe1\xaf\x94\x79\x67\x17\xdd\xee\x79\xe1\x45\x52\x13\x42\x54\xe5\x44\xf5\xf1\x5b\x5a\x8a\xcf\x88\xa7\x7a\x55\x86\x0f\xc8\x8e\x33\xa4\x68\x26\x16\xc7\xc9\x66\x99\xa5\x32\x90\xcb\xce\x39\x0c\x8d\x1b\x7d\x31\xb6\xa0\x6e\x7e\xd1\xeb\xbd\x5e\x49\xa8\x0c\x64\x29\x99\xc7\x21\xb9\x96\xb6\xa9\x6b\x0b\x7c\xdc\xe0\x39\x37\x7e\x2e\x0c\x73\xf0\x1e\x48\x37\x4f\x76\x68\x4e\x42\x9f\x5b\xfa\xcc\x4a\x29\xa3\x9d\x1c\x8c\xcc\xc1\xcc\x48\x1a\xfc\xfb\xdd\xfa\xc8\x9c\xc2\x64\x64\xfe\xa5\x8f\x17\xc6\xdb\xb7\xfe\x7b\xfb\x3d\xd0\x07\x3f\x0c\x68\xef\x0a\x3f\x6f\xf5\x24\x49\x48\x94\x0c\xe1\xfb\xdd\x31\xd9\x48\x7d\x3a\x90\x8c\x37\x8d\x25\xd4\x9a\xd4\x2d\x72\xa7\x48\xa6\xd7\xb6\x68\x3e\xd7\x73\x45\x6d\xdb\x9e\xa3\x4c\xa2\x87\xfc\x23\xd5\x7a\x64\x33\xb4\x5b\x25\xd1\x30\xe6\x0e\x56\x88\x76\x2f\x4b\xa2\xf1\x32\xa0\x52\xbe\xb7\x97\x45\xec\x38\x88\xee\xc1\x67\xa9\xb4\x8f\xae\x4a\x41\x4a\x3f\xe9\x11\xb2\x27\xf4\xc3\x08\xed\x17\x97\x57\xb1\x2e\x47\xa1\xa6\x82\xba\xdd\xd1\xfe\x9b\x5e\x56\x6f\x2a\x5a\x79\xc3\x39\x9b\x02\xf9\xa0\x9a\x32\x23\x94\xa0\xde\xdf\x0c\x4a\x3c\x8f\xf6\xa9\x18\xa1\x5a\xf3\x7f\xcd\x08\xd5\x56\x4f\x92\x84\xcf\x30\xc2\x1e\xb7\x12\x46\x28\xa2\x56\xc1\x08\x25\xaa\xa7\xee\xc6\xc3\x75\x91\x1c\xef\x57\x9a\xab\xa9\x39\x2e\x73\x11\xa4\x12\x83\xe9\x78\x31\x31\x15\x43\xa8\x2d\x68\x53\x85\x0c\x5f\xe4\x13\xf1\xcf\xea\xd5\x0c\x57\xef\xf5\x38\x7a\x8e\x4f\x84\x38\x3f\x44\x68\x27\x72\xbf\xa4\xf6\x28\xf7\xab\xe1\xa8\x76\x7f\x48\x24\x81\x55\xc8\x8f\x58\xf4\x60\xa8\x5b\xfa\x51\xbd\x32\xbd\xbd\x2b\x6f\x79\xd4\xd5\x32\xeb\xdc\x39\xdc\xcc\xa6\x13\x10\xd2\xa5\xac\x5f\x6b\xb5\x6d\xca\xa8\x6c\x8a\x47\xff\x7f\x17\xad\xf6\x55\xa3\xd5\x69\x5c\x74\xa1\x7d\xd5\xbb\xe8\xf4\x3a\x9d\x66\xb7\xfb\xf5\xdb\x55\xfb\xff\xad\x8b\x5a\xeb\xc2\xde\xa2\xb2\x5b\xfe\x6b\xfb\xb2\xd3\x4d\xe4\xff\xb0\xf3\xa9\xd8\xf3\xe6\xea\xeb\xb7\xf4\x4d\x27\x5d\x97\x6d\x46\x02\x14\xbb\x1f\x5c\xb5\x3b\x4a\xfc\xef\xe6\xae\x6c\xee\x5d\x2d\xdf\x97\xce\xf2\x5e\xa9\xf2\x99\x5d\x1f\xb5\xdc\x6a\xa8\xe5\x36\x3e\x6d\x3d\xc0\xb4\xc2\xec\xcb\xa3\x90\x09\xe4\x5d\xbb\xc4\xdc\x38\xaa\xba\xe7\xc6\xd8\x18\x58\x99\x9f\x28\x4d\x81\x7b\xfb\x4d\x83\xb6\x96\xfe\x1c\xd9\x5d\xb0\x7b\x37\x89\xf7\xa5\xb8\xbc\x46\xa8\xa4\xe6\x13\xb6\x4d\x64\x3a\xe6\xb5\xdc\x0c\xd7\x72\xa3\x59\x5b\x8f\x5e\x2d\x37\x6f\xb5\xcc\x74\xd5\x32\x23\xf2\x00\x14\x47\x92\xf8\xa7\xa1\xd8\xc1\x1d\x95\x50\x54\x52\x46\xf1\xdb\x8e\x1e\xf0\x75\xfb\xd3\xd8\x9c\x5b\x33\x7d\x64\x7e\x8c\x0e\x0b\xaa\x93\x49\xa9\x0f\x87\x19\xb5\x95\xd6\xe1\x76\x36\x9a\xe8\xb3\x3b\xf8\xd3\xb8\x53\xa0\x1e\x66\xef\x4c\x21\xc6\x8c\x3e\xc6\x78\xa2\x00\x8a\x86\xaa\x22\x28\xd9\x86\x85\x39\xfa\xb9\x30\xe0\x2c\x53\x2c\x07\xc7\x67\xb1\xa5\xb2\x4c\x61\x9f\x0e\x91\xa2\xdd\xaa\x80\x0e\xb9\xf2\x16\x5f\xf6\xe2\xfd\x11\x9e\xb0\xce\x3e\x12\xd5\xc7\x0a\xad\xd8\x6e\x27\x0c\xe2\x98\x5a\xab\x32\xbf\x27\x88\x5d\x7f\x72\x82\x13\x06\x91\x8f\x12\x13\x4f\xfe\x0d\x00\x00\xff\xff\x7e\x47\x0b\xb3\x11\x15\x00\x00") - -func latestSqlBytes() ([]byte, error) { - return bindataRead( - _latestSql, - "latest.sql", - ) -} - -func latestSql() (*asset, error) { - bytes, err := latestSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "latest.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8e, 0x1a, 0xb5, 0xc5, 0xcf, 0x93, 0xea, 0xf9, 0xfa, 0x2b, 0xff, 0xa4, 0x69, 0x26, 0xf6, 0xd2, 0x64, 0x1e, 0xe3, 0x4d, 0x3f, 0x33, 0x7a, 0xd7, 0x2e, 0xbc, 0xea, 0xbc, 0xa3, 0xc3, 0x9e, 0x32}} - return a, nil -} - -var _migrations01_initSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x92\x41\x4f\xfa\x40\x10\xc5\xef\xfb\x29\xe6\x08\xf9\xff\x49\xd4\x08\x17\x4e\x55\x6a\x42\xac\x80\xb5\x3d\x70\x6a\x86\xdd\x49\x9d\xd8\xee\x36\xbb\x53\xc4\x6f\x6f\x30\x51\xda\x82\x9e\x7f\x2f\x33\xef\xbd\x99\xc9\x04\xfe\xd5\x5c\x7a\x14\x82\xbc\x51\xf7\x69\x1c\x65\x31\x64\xd1\x5d\x12\x43\x4a\x9a\x78\x4f\x66\x83\x1f\x35\x59\x81\x91\x02\x60\x03\x3b\x2e\x03\x79\xc6\xea\xbf\x02\x70\x0d\x79\x14\x76\xb6\x60\x03\x7b\xf4\xfa\x15\xfd\xe8\x66\x3a\x1d\x43\xbe\x5a\x3e\xe7\x31\xac\xd6\x19\xac\xf2\x24\x39\x8a\x1b\xef\x34\x85\x40\xa6\x40\x01\xe1\x9a\x82\x60\xdd\xf4\x25\x58\xb2\x2d\x0b\x71\x6f\x64\xfb\xf3\xba\xaa\x20\x28\x6d\xf8\x9d\x6f\xd2\xe5\x53\x94\x6e\xe1\x31\xde\xc2\x88\xcd\x58\x8d\xe7\xaa\x9f\xed\x85\xac\x64\x1e\x6d\x40\x7d\x74\xff\x9d\xed\x14\x4c\x4e\xb0\x1b\x6d\x76\xdb\xd9\x04\xe7\x56\xae\xaf\x06\x4e\x5d\xeb\x35\xfd\xe0\xe9\x6c\x80\xdb\x5d\xcd\x22\x7f\x35\x12\x5a\xad\x89\xcc\x50\xb2\x88\x1f\xa2\x3c\x39\xc9\x2a\x32\x25\xf9\xe3\x71\xd8\xca\x19\x25\xbb\xa7\xca\x35\x54\x1c\x8c\x07\xa1\x83\xf4\x56\x78\x0a\x6d\x25\x5f\xac\x57\xe9\x70\xca\xc5\x5a\xbb\x1f\xb4\x70\xef\x56\x2d\xd2\xf5\xe6\xf2\x07\xcd\xbb\x6c\x70\x81\xb9\xfa\x0c\x00\x00\xff\xff\x4d\x61\x55\x6b\x8b\x02\x00\x00") - -func migrations01_initSqlBytes() ([]byte, error) { - return bindataRead( - _migrations01_initSql, - "migrations/01_init.sql", - ) -} - -func migrations01_initSql() (*asset, error) { - bytes, err := migrations01_initSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/01_init.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1a, 0x1, 0x93, 0xc5, 0x97, 0xe2, 0xe7, 0x8c, 0xc5, 0x48, 0xdf, 0xc, 0x6f, 0xcf, 0x13, 0x1f, 0x7e, 0x4, 0xfb, 0x30, 0xb7, 0x8f, 0xd0, 0xa8, 0xd4, 0x94, 0x77, 0x28, 0xf, 0xe1, 0x3d, 0x77}} - return a, nil -} - -var _migrations02_payment_idSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\xce\x31\x0b\xc2\x30\x10\x05\xe0\x3d\xbf\xe2\xc6\x16\xe9\x22\x74\xea\x74\x36\x11\x0b\x21\xd5\xf4\xe2\x5a\x82\x06\xc9\xd0\x6b\xad\x29\xe2\xbf\x17\x5c\xec\xa4\xe3\xe3\xc1\xf7\x5e\x51\xc0\x66\x88\xb7\xd9\xa7\x00\x6e\x12\xa8\x49\x59\x20\xdc\x69\x05\x5d\xe0\x44\xb3\xe7\x87\xbf\xa4\x38\x32\xa0\x94\x30\xf9\xd7\x10\x38\xf5\xf1\x0a\x67\xb4\xf5\x01\x6d\xb6\x2d\xcb\x1c\x8c\xd3\x1a\xa4\xda\xa3\xd3\xf4\x09\xd5\x5f\xaa\x6e\x4d\x47\x16\x1b\x43\x2b\xb5\x5f\x38\xde\x97\x00\xce\x34\x27\xa7\x20\xfb\x36\x79\x25\xc4\xfa\xac\x1c\x9f\xfc\x73\x43\xda\xf6\xb8\x92\x2b\xf1\x0e\x00\x00\xff\xff\xd0\xf6\xe1\x10\xeb\x00\x00\x00") - -func migrations02_payment_idSqlBytes() ([]byte, error) { - return bindataRead( - _migrations02_payment_idSql, - "migrations/02_payment_id.sql", - ) -} - -func migrations02_payment_idSql() (*asset, error) { - bytes, err := migrations02_payment_idSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/02_payment_id.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4, 0xa9, 0x7f, 0xcb, 0x38, 0x71, 0x17, 0xf3, 0x1b, 0x22, 0x5c, 0x31, 0x2f, 0xee, 0x48, 0xca, 0xf6, 0x20, 0x5d, 0x1e, 0xc8, 0x19, 0x54, 0x5d, 0x6d, 0x5c, 0x56, 0x95, 0x5, 0x3c, 0xa, 0x2d}} - return a, nil -} - -var _migrations03_transaction_idSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd2\xd5\x55\xd0\xce\xcd\x4c\x2f\x4a\x2c\x49\x55\x08\x2d\xe0\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x08\x4a\x4d\x4e\xcd\x2c\x4b\x4d\x09\x48\xac\xcc\x4d\xcd\x2b\x51\x70\x74\x71\x51\x28\x29\x4a\xcc\x2b\x4e\x4c\x2e\xc9\xcc\xcf\x8b\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x33\xd1\x54\x70\x71\x75\x73\x0c\xf5\x09\x51\x50\xf7\xd3\x77\x54\xb7\xe6\xe2\x42\x36\xd9\x25\xbf\x3c\x0f\xaf\xd9\x2e\x41\xfe\x01\x68\x86\x5b\x73\x01\x02\x00\x00\xff\xff\x95\xd0\x87\x98\x9c\x00\x00\x00") - -func migrations03_transaction_idSqlBytes() ([]byte, error) { - return bindataRead( - _migrations03_transaction_idSql, - "migrations/03_transaction_id.sql", - ) -} - -func migrations03_transaction_idSql() (*asset, error) { - bytes, err := migrations03_transaction_idSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/03_transaction_id.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf0, 0x8, 0x15, 0xf2, 0xc, 0x51, 0x90, 0x8, 0x9f, 0xc9, 0xac, 0x8, 0x24, 0xf9, 0x21, 0x7d, 0x7f, 0x19, 0xdf, 0x71, 0xcb, 0xff, 0xe8, 0xf7, 0x9, 0x6b, 0xe8, 0x10, 0x3, 0x17, 0x26, 0x28}} - return a, nil -} - -var _migrations04_table_namesSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd2\xd5\x55\xd0\xce\xcd\x4c\x2f\x4a\x2c\x49\x55\x08\x2d\xe0\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x08\x4a\x4d\x4e\xcd\x2c\x4b\x4d\x09\x48\xac\xcc\x4d\xcd\x2b\x51\x08\x72\xf5\x73\xf4\x75\x55\x08\xf1\x57\x28\x82\xca\xc4\x17\x40\xa4\xac\x51\xf4\x05\xa7\xe6\x95\x84\x14\x25\xe6\x15\x27\x26\x97\x64\xe6\xe7\x21\xe9\x2b\x4e\xcd\x2b\x89\x2f\x41\x48\x59\x73\x71\x21\xdb\xef\x92\x5f\x9e\x87\x62\x12\xba\x3d\x48\x46\xa1\x39\x0e\xd5\x05\xe8\xf6\x20\xe9\x43\x73\x9c\x35\x17\x20\x00\x00\xff\xff\xb7\xf3\x31\x25\x01\x01\x00\x00") - -func migrations04_table_namesSqlBytes() ([]byte, error) { - return bindataRead( - _migrations04_table_namesSql, - "migrations/04_table_names.sql", - ) -} - -func migrations04_table_namesSql() (*asset, error) { - bytes, err := migrations04_table_namesSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/04_table_names.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xdd, 0xc, 0xd1, 0x5d, 0x33, 0x1, 0x49, 0x15, 0x2, 0xca, 0x84, 0xdd, 0xcf, 0xea, 0xbe, 0x38, 0xa8, 0x7, 0xae, 0x43, 0x5b, 0xf5, 0x8b, 0xd3, 0xce, 0xf, 0x79, 0xcd, 0xa3, 0x7b, 0x0, 0xed}} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "latest.sql": latestSql, - "migrations/01_init.sql": migrations01_initSql, - "migrations/02_payment_id.sql": migrations02_payment_idSql, - "migrations/03_transaction_id.sql": migrations03_transaction_idSql, - "migrations/04_table_names.sql": migrations04_table_namesSql, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "latest.sql": &bintree{latestSql, map[string]*bintree{}}, - "migrations": &bintree{nil, map[string]*bintree{ - "01_init.sql": &bintree{migrations01_initSql, map[string]*bintree{}}, - "02_payment_id.sql": &bintree{migrations02_payment_idSql, map[string]*bintree{}}, - "03_transaction_id.sql": &bintree{migrations03_transaction_idSql, map[string]*bintree{}}, - "04_table_names.sql": &bintree{migrations04_table_namesSql, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory. -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) -} - -// RestoreAssets restores an asset under the given directory recursively. -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) -} diff --git a/services/bridge/internal/db/latest.sql b/services/bridge/internal/db/latest.sql deleted file mode 100644 index c4ecb65227..0000000000 --- a/services/bridge/internal/db/latest.sql +++ /dev/null @@ -1,226 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 9.6.1 --- Dumped by pg_dump version 9.6.1 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SET check_function_bodies = false; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: --- - -CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; - - --- --- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; - - -SET search_path = public, pg_catalog; - -SET default_tablespace = ''; - -SET default_with_oids = false; - --- --- Name: gorp_migrations; Type: TABLE; Schema: public; Owner: bartek --- - -CREATE TABLE gorp_migrations ( - id text NOT NULL, - applied_at timestamp with time zone -); - - -ALTER TABLE gorp_migrations OWNER TO bartek; - --- --- Name: received_payment; Type: TABLE; Schema: public; Owner: bartek --- - -CREATE TABLE received_payment ( - id bigint NOT NULL, - operation_id character varying(255) NOT NULL, - processed_at timestamp without time zone NOT NULL, - paging_token character varying(255) NOT NULL, - status character varying(255) NOT NULL, - transaction_id character varying(64) DEFAULT 'N/A'::character varying -); - - -ALTER TABLE received_payment OWNER TO bartek; - --- --- Name: receivedpayment_id_seq; Type: SEQUENCE; Schema: public; Owner: bartek --- - -CREATE SEQUENCE receivedpayment_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE receivedpayment_id_seq OWNER TO bartek; - --- --- Name: receivedpayment_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: bartek --- - -ALTER SEQUENCE receivedpayment_id_seq OWNED BY received_payment.id; - - --- --- Name: sent_transaction; Type: TABLE; Schema: public; Owner: bartek --- - -CREATE TABLE sent_transaction ( - id integer NOT NULL, - transaction_id character varying(64) NOT NULL, - status character varying(10) NOT NULL, - source character varying(56) NOT NULL, - submitted_at timestamp without time zone NOT NULL, - succeeded_at timestamp without time zone, - ledger bigint, - envelope_xdr text NOT NULL, - result_xdr character varying(255) DEFAULT NULL::character varying, - payment_id character varying(255) DEFAULT NULL::character varying -); - - -ALTER TABLE sent_transaction OWNER TO bartek; - --- --- Name: senttransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: bartek --- - -CREATE SEQUENCE senttransaction_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE senttransaction_id_seq OWNER TO bartek; - --- --- Name: senttransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: bartek --- - -ALTER SEQUENCE senttransaction_id_seq OWNED BY sent_transaction.id; - - --- --- Name: received_payment id; Type: DEFAULT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY received_payment ALTER COLUMN id SET DEFAULT nextval('receivedpayment_id_seq'::regclass); - - --- --- Name: sent_transaction id; Type: DEFAULT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY sent_transaction ALTER COLUMN id SET DEFAULT nextval('senttransaction_id_seq'::regclass); - - --- --- Data for Name: gorp_migrations; Type: TABLE DATA; Schema: public; Owner: bartek --- - -COPY gorp_migrations (id, applied_at) FROM stdin; -01_init.sql 2018-04-25 18:24:44.557981+02 -02_payment_id.sql 2018-04-25 18:24:44.571645+02 -03_transaction_id.sql 2018-04-25 18:24:44.578795+02 -04_table_names.sql 2018-04-25 18:24:44.5814+02 -\. - - --- --- Data for Name: received_payment; Type: TABLE DATA; Schema: public; Owner: bartek --- - -COPY received_payment (id, operation_id, processed_at, paging_token, status, transaction_id) FROM stdin; -\. - - --- --- Name: receivedpayment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: bartek --- - -SELECT pg_catalog.setval('receivedpayment_id_seq', 1, false); - - --- --- Data for Name: sent_transaction; Type: TABLE DATA; Schema: public; Owner: bartek --- - -COPY sent_transaction (id, transaction_id, status, source, submitted_at, succeeded_at, ledger, envelope_xdr, result_xdr, payment_id) FROM stdin; -\. - - --- --- Name: senttransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: bartek --- - -SELECT pg_catalog.setval('senttransaction_id_seq', 1, false); - - --- --- Name: gorp_migrations gorp_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY gorp_migrations - ADD CONSTRAINT gorp_migrations_pkey PRIMARY KEY (id); - - --- --- Name: sent_transaction payment_id_unique; Type: CONSTRAINT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY sent_transaction - ADD CONSTRAINT payment_id_unique UNIQUE (payment_id); - - --- --- Name: received_payment receivedpayment_operation_id_key; Type: CONSTRAINT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY received_payment - ADD CONSTRAINT receivedpayment_operation_id_key UNIQUE (operation_id); - - --- --- Name: received_payment receivedpayment_pkey; Type: CONSTRAINT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY received_payment - ADD CONSTRAINT receivedpayment_pkey PRIMARY KEY (id); - - --- --- Name: sent_transaction senttransaction_pkey; Type: CONSTRAINT; Schema: public; Owner: bartek --- - -ALTER TABLE ONLY sent_transaction - ADD CONSTRAINT senttransaction_pkey PRIMARY KEY (id); - - --- --- PostgreSQL database dump complete --- - diff --git a/services/bridge/internal/db/main.go b/services/bridge/internal/db/main.go deleted file mode 100644 index becf9081ad..0000000000 --- a/services/bridge/internal/db/main.go +++ /dev/null @@ -1,73 +0,0 @@ -package db - -import ( - "database/sql" - "time" - - migrate "github.com/rubenv/sql-migrate" - "github.com/stellar/go/support/db" -) - -//go:generate go-bindata -nometadata -ignore .+\.go$ -pkg db -o bindata.go ./... - -// Migrations represents all of the schema migration -var Migrations migrate.MigrationSource = &migrate.AssetMigrationSource{ - Asset: Asset, - AssetDir: AssetDir, - Dir: "migrations", -} - -type Database interface { - GetLastCursorValue() (cursor *string, err error) - - InsertReceivedPayment(payment *ReceivedPayment) error - UpdateReceivedPayment(payment *ReceivedPayment) error - GetReceivedPaymentByID(id int64) (*ReceivedPayment, error) - GetReceivedPaymentByOperationID(operationID string) (*ReceivedPayment, error) - GetReceivedPayments(page, limit uint64) ([]*ReceivedPayment, error) - - InsertSentTransaction(transaction *SentTransaction) error - UpdateSentTransaction(transaction *SentTransaction) error - GetSentTransactionByPaymentID(paymentID string) (*SentTransaction, error) - GetSentTransactions(page, limit uint64) ([]*SentTransaction, error) -} - -type PostgresDatabase struct { - session *db.Session -} - -// ReceivedPayment represents payment received by the gateway server -type ReceivedPayment struct { - ID int64 `db:"id" json:"id"` - OperationID string `db:"operation_id" json:"operation_id"` - ProcessedAt time.Time `db:"processed_at" json:"processed_at"` - PagingToken string `db:"paging_token" json:"paging_token"` - Status string `db:"status" json:"status"` - TransactionID string `db:"transaction_id" json:"transaction_id"` -} - -// SentTransactionStatus type represents sent transaction status -type SentTransactionStatus string - -const ( - // SentTransactionStatusSending is a status indicating that transaction is sending - SentTransactionStatusSending SentTransactionStatus = "sending" - // SentTransactionStatusSuccess is a status indicating that transaction has been successfully sent - SentTransactionStatusSuccess SentTransactionStatus = "success" - // SentTransactionStatusFailure is a status indicating that there has been an error while sending a transaction - SentTransactionStatusFailure SentTransactionStatus = "failure" -) - -// SentTransaction represents transaction sent by the gateway server -type SentTransaction struct { - ID int64 `db:"id" json:"id"` - PaymentID sql.NullString `db:"payment_id" json:"payment_id"` - TransactionID string `db:"transaction_id" json:"transaction_id"` - Status SentTransactionStatus `db:"status" json:"status"` // sending/success/failure - Source string `db:"source" json:"source"` - SubmittedAt time.Time `db:"submitted_at" json:"submitted_at"` - SucceededAt *time.Time `db:"succeeded_at" json:"succeeded_at"` - Ledger *int32 `db:"ledger" json:"ledger"` - EnvelopeXdr string `db:"envelope_xdr" json:"envelope_xdr"` - ResultXdr *string `db:"result_xdr" json:"result_xdr"` -} diff --git a/services/bridge/internal/db/migrations/01_init.sql b/services/bridge/internal/db/migrations/01_init.sql deleted file mode 100644 index 2b090ae312..0000000000 --- a/services/bridge/internal/db/migrations/01_init.sql +++ /dev/null @@ -1,26 +0,0 @@ --- +migrate Up -CREATE TABLE ReceivedPayment ( - id bigserial, - operation_id varchar(255) UNIQUE NOT NULL, - processed_at timestamp NOT NULL, - paging_token varchar(255) NOT NULL, - status varchar(255) NOT NULL, - PRIMARY KEY (id) -); - -CREATE TABLE SentTransaction ( - id serial, - transaction_id varchar(64) NOT NULL, - status varchar(10) NOT NULL, - source varchar(56) NOT NULL, - submitted_at timestamp NOT NULL, - succeeded_at timestamp DEFAULT NULL, - ledger bigint DEFAULT NULL, - envelope_xdr text NOT NULL, - result_xdr varchar(255) DEFAULT NULL, - PRIMARY KEY (id) -); - --- +migrate Down -DROP TABLE ReceivedPayment; -DROP TABLE SentTransaction; diff --git a/services/bridge/internal/db/migrations/02_payment_id.sql b/services/bridge/internal/db/migrations/02_payment_id.sql deleted file mode 100644 index b4a917536e..0000000000 --- a/services/bridge/internal/db/migrations/02_payment_id.sql +++ /dev/null @@ -1,6 +0,0 @@ --- +migrate Up -ALTER TABLE SentTransaction ADD payment_id VARCHAR(255) NULL DEFAULT NULL; -ALTER TABLE SentTransaction ADD CONSTRAINT payment_id_unique UNIQUE (payment_id); - --- +migrate Down -ALTER TABLE SentTransaction DROP payment_id; diff --git a/services/bridge/internal/db/migrations/03_transaction_id.sql b/services/bridge/internal/db/migrations/03_transaction_id.sql deleted file mode 100644 index d2dba347d3..0000000000 --- a/services/bridge/internal/db/migrations/03_transaction_id.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +migrate Up -ALTER TABLE ReceivedPayment ADD transaction_id VARCHAR(64) DEFAULT 'N/A'; - --- +migrate Down -ALTER TABLE ReceivedPayment DROP transaction_id; diff --git a/services/bridge/internal/db/migrations/04_table_names.sql b/services/bridge/internal/db/migrations/04_table_names.sql deleted file mode 100644 index 7aa9cc0d44..0000000000 --- a/services/bridge/internal/db/migrations/04_table_names.sql +++ /dev/null @@ -1,7 +0,0 @@ --- +migrate Up -ALTER TABLE ReceivedPayment RENAME TO received_payment; -ALTER TABLE SentTransaction RENAME TO sent_transaction; - --- +migrate Down -ALTER TABLE received_payment RENAME TO ReceivedPayment; -ALTER TABLE sent_transaction RENAME TO SentTransaction; diff --git a/services/bridge/internal/db/migrations_test.go b/services/bridge/internal/db/migrations_test.go deleted file mode 100644 index 0d9d270d62..0000000000 --- a/services/bridge/internal/db/migrations_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package db - -import ( - "net/http" - "os" - "strings" - "testing" - - assetfs "github.com/elazarl/go-bindata-assetfs" - "github.com/shurcooL/httpfs/filter" - - supportHttp "github.com/stellar/go/support/http" -) - -func TestGeneratedAssets(t *testing.T) { - localAssets := filter.Keep(http.Dir("."), func(path string, fi os.FileInfo) bool { - return fi.IsDir() || strings.HasSuffix(path, ".sql") - }) - generatedAssets := &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo} - if !supportHttp.EqualFileSystems(localAssets, generatedAssets, "/") { - t.Fatalf("generated migrations does not match local migrations") - } -} diff --git a/services/bridge/internal/db/postgres.go b/services/bridge/internal/db/postgres.go deleted file mode 100644 index 8c4c490d40..0000000000 --- a/services/bridge/internal/db/postgres.go +++ /dev/null @@ -1,251 +0,0 @@ -package db - -import ( - "database/sql" - - "github.com/stellar/go/support/db" - "github.com/stellar/go/support/errors" -) - -const ( - receivedPaymentTableName = "received_payment" - sentTransactionTableName = "sent_transaction" -) - -func (d *PostgresDatabase) Open(dsn string) error { - var err error - d.session, err = db.Open("postgres", dsn) - if err != nil { - return err - } - - return nil -} - -func (d *PostgresDatabase) GetDB() *sql.DB { - if d.session == nil { - return nil - } - - return d.session.DB.DB -} - -func (d *PostgresDatabase) getTable(name string, session *db.Session) *db.Table { - if session == nil { - session = d.session - } - - return &db.Table{ - Name: name, - Session: session, - } -} - -// InsertSentTransaction inserts anew transaction into DB. After successful insert ID -// field on `transaction` will updated to ID of a new row. -func (d *PostgresDatabase) InsertSentTransaction(transaction *SentTransaction) error { - sentTransactionTable := d.getTable(sentTransactionTableName, nil) - _, err := sentTransactionTable.Insert(transaction).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting sent transaction") - } - - newTransaction, err := d.GetSentTransactionByHash(transaction.TransactionID) - if err != nil { - return errors.Wrap(err, "Error getting new transaction") - } - - transaction.ID = newTransaction.ID - return nil -} - -func (d *PostgresDatabase) UpdateSentTransaction(transaction *SentTransaction) error { - if transaction.ID == 0 { - return errors.New("ID equals 0") - } - - sentTransactionTable := d.getTable(sentTransactionTableName, nil) - // TODO: something's wrong with db.Table.Update(). Setting the first argument does not work as expected. - _, err := sentTransactionTable.Update(nil, map[string]interface{}{"id": transaction.ID}). - SetStruct(transaction, []string{"id"}). - Exec() - if err != nil { - return errors.Wrap(err, "Error updating sent transaction") - } - - return nil -} - -// InsertReceivedPayment inserts a new payment into DB. After successful insert ID -// field on `payment` will updated to ID of a new row. -func (d *PostgresDatabase) InsertReceivedPayment(payment *ReceivedPayment) error { - receivedPaymentTable := d.getTable(receivedPaymentTableName, nil) - _, err := receivedPaymentTable.Insert(payment).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting received payment") - } - - newPayment, err := d.GetReceivedPaymentByOperationID(payment.OperationID) - if err != nil { - return errors.Wrap(err, "Error getting new operation") - } - - payment.ID = newPayment.ID - return nil -} - -func (d *PostgresDatabase) UpdateReceivedPayment(payment *ReceivedPayment) error { - if payment.ID == 0 { - return errors.New("ID equals 0") - } - - receivedPaymentTable := d.getTable(receivedPaymentTableName, nil) - // TODO: something's wrong with db.Table.Update(). Setting the first argument does not work as expected. - _, err := receivedPaymentTable.Update(nil, map[string]interface{}{"id": payment.ID}). - SetStruct(payment, []string{"id"}). - Exec() - if err != nil { - return errors.Wrap(err, "Error updating received payment") - } - - return nil -} - -// GetLastCursorValue returns last cursor value from a DB -func (d *PostgresDatabase) GetLastCursorValue() (cursor *string, err error) { - receivedPayment, err := d.getLastReceivedPayment() - if err != nil { - return nil, err - } else if receivedPayment == nil { - return nil, nil - } else { - return &receivedPayment.PagingToken, nil - } -} - -// GetSentTransactionByPaymentID returns sent transaction searching by payment ID -func (d *PostgresDatabase) GetSentTransactionByPaymentID(paymentID string) (*SentTransaction, error) { - sentTransactionTable := d.getTable(sentTransactionTableName, nil) - var transaction SentTransaction - err := sentTransactionTable.Get(&transaction, map[string]interface{}{"payment_id": paymentID}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting sent transaction by payment ID") - } - } - - return &transaction, nil -} - -// GetSentTransactionByHash returns sent transaction searching by hash -func (d *PostgresDatabase) GetSentTransactionByHash(hash string) (*SentTransaction, error) { - sentTransactionTable := d.getTable(sentTransactionTableName, nil) - var transaction SentTransaction - err := sentTransactionTable.Get(&transaction, map[string]interface{}{"transaction_id": hash}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting sent transaction by hash") - } - } - - return &transaction, nil -} - -// GetReceivedPaymentByID returns received payment by id -func (d *PostgresDatabase) GetReceivedPaymentByID(id int64) (*ReceivedPayment, error) { - return d.getReceivedPayment(map[string]interface{}{"id": id}) -} - -// GetReceivedPaymentByOperationID returns received payment by operation_id -func (d *PostgresDatabase) GetReceivedPaymentByOperationID(operationID string) (*ReceivedPayment, error) { - return d.getReceivedPayment(map[string]interface{}{"operation_id": operationID}) -} - -func (d *PostgresDatabase) getReceivedPayment(params map[string]interface{}) (*ReceivedPayment, error) { - receivedPaymentTable := d.getTable(receivedPaymentTableName, nil) - var receivedPayment ReceivedPayment - err := receivedPaymentTable.Get(&receivedPayment, params).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting received payment transaction by operation ID") - } - } - - return &receivedPayment, nil -} - -// GetReceivedPayments returns received payments -func (d *PostgresDatabase) GetReceivedPayments(page, limit uint64) ([]*ReceivedPayment, error) { - receivedPaymentTable := d.getTable(receivedPaymentTableName, nil) - payments := []*ReceivedPayment{} - - if page == 0 { - page = 1 - } - - offset := (page - 1) * limit - - err := receivedPaymentTable.Select(&payments, "1=1").Limit(limit).Offset(offset).OrderBy("id desc").Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return payments, nil - default: - return payments, errors.Wrap(err, "Error getting received payments") - } - } - - return payments, nil -} - -// GetSentTransactions returns received payments -func (d *PostgresDatabase) GetSentTransactions(page, limit uint64) ([]*SentTransaction, error) { - sentTransactionTable := d.getTable(sentTransactionTableName, nil) - transactions := []*SentTransaction{} - - if page == 0 { - page = 1 - } - - offset := (page - 1) * limit - - err := sentTransactionTable.Select(&transactions, "1=1").Limit(limit).Offset(offset).OrderBy("id desc").Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return transactions, nil - default: - return transactions, errors.Wrap(err, "Error getting sent transactions") - } - } - - return transactions, nil -} - -// getLastReceivedPayment returns the last received payment -func (d *PostgresDatabase) getLastReceivedPayment() (*ReceivedPayment, error) { - receivedPaymentTable := d.getTable(receivedPaymentTableName, nil) - // TODO: `1=1`. We should be able to get row without WHERE clause. - // When it's set to nil: `pq: syntax error at or near "ORDER""` - var receivedPayment ReceivedPayment - err := receivedPaymentTable.Get(&receivedPayment, "1=1").OrderBy("id DESC").Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting last received payment") - } - } - - return &receivedPayment, nil -} diff --git a/services/bridge/internal/db/sent_transaction.go b/services/bridge/internal/db/sent_transaction.go deleted file mode 100644 index 449e0ea676..0000000000 --- a/services/bridge/internal/db/sent_transaction.go +++ /dev/null @@ -1,24 +0,0 @@ -package db - -import ( - "database/sql/driver" - - "github.com/stellar/go/support/errors" -) - -// Scan implements database/sql.Scanner interface -func (s *SentTransactionStatus) Scan(src interface{}) error { - value, ok := src.(string) - if !ok { - return errors.New("Cannot convert value to SentTransactionStatus") - } - *s = SentTransactionStatus(value) - return nil -} - -// Value implements driver.Valuer -func (status SentTransactionStatus) Value() (driver.Value, error) { - return driver.Value(string(status)), nil -} - -var _ driver.Valuer = SentTransactionStatus("") diff --git a/services/bridge/internal/handlers/main.go b/services/bridge/internal/handlers/main.go deleted file mode 100644 index b5db910d93..0000000000 --- a/services/bridge/internal/handlers/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package handlers - -import ( - "github.com/stellar/go/clients/federation" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/bridge/internal/listener" - "github.com/stellar/go/services/bridge/internal/submitter" - "github.com/stellar/go/support/http" -) - -// RequestHandler implements bridge server request handlers -type RequestHandler struct { - Config *config.Config `inject:""` - Client http.SimpleHTTPClientInterface `inject:""` - Horizon hc.ClientInterface `inject:""` - Database db.Database `inject:""` - StellarTomlResolver stellartoml.ClientInterface `inject:""` - FederationResolver federation.ClientInterface `inject:""` - TransactionSubmitter submitter.TransactionSubmitterInterface `inject:""` - PaymentListener *listener.PaymentListener `inject:""` -} diff --git a/services/bridge/internal/handlers/request_handler_admin.go b/services/bridge/internal/handlers/request_handler_admin.go deleted file mode 100644 index 22c76decf4..0000000000 --- a/services/bridge/internal/handlers/request_handler_admin.go +++ /dev/null @@ -1,154 +0,0 @@ -package handlers - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "strconv" - - "github.com/go-chi/chi" - log "github.com/sirupsen/logrus" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/support/errors" -) - -// AdminReceivedPayment implements /admin/received-payments/{id} endpoint -func (rh *RequestHandler) AdminReceivedPayment(w http.ResponseWriter, r *http.Request) { - id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) - payment, err := rh.Database.GetReceivedPaymentByID(id) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error getting ReceivedPayments") - helpers.Write(w, helpers.InternalServerError) - return - } - - if payment == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - paymentResponse, err := rh.Horizon.OperationDetail(payment.OperationID) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error getting operation from Horizon") - helpers.Write(w, helpers.InternalServerError) - return - } - - bridgePayment, err := rh.PaymentListener.ConvertToBridgePayment(paymentResponse) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error converting operation to bridge payment type") - helpers.Write(w, helpers.InternalServerError) - return - } - - var authData *compliance.AuthData - if bridgePayment.MemoType == "hash" && rh.Config.Compliance != "" { - authData, err = rh.getComplianceData(bridgePayment.Memo) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error loading compliance data") - helpers.Write(w, helpers.InternalServerError) - return - } - } - - response := struct { - Payment *db.ReceivedPayment `json:"payment"` - Operation bridge.PaymentResponse `json:"operation"` - AuthData *compliance.AuthData `json:"auth_data"` - }{payment, bridgePayment, authData} - - encoder := json.NewEncoder(w) - err = encoder.Encode(response) - if err != nil { - log.WithFields(log.Fields{"err": err, "payments": payment}).Error("Error encoding ReceivedPayment") - helpers.Write(w, helpers.InternalServerError) - return - } -} - -func (rh *RequestHandler) getComplianceData(memo string) (*compliance.AuthData, error) { - complianceRequestURL := rh.Config.Compliance + "/receive" - complianceRequestBody := url.Values{"memo": {string(memo)}} - - log.WithFields(log.Fields{"url": complianceRequestURL, "body": complianceRequestBody}).Info("Sending request to compliance server") - resp, err := rh.Client.PostForm(complianceRequestURL, complianceRequestBody) - if err != nil { - return nil, errors.Wrap(err, "Error sending request to compliance server") - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, errors.Wrap(err, "Error reading compliance server response") - } - - if resp.StatusCode == http.StatusNotFound { - return nil, nil - } - - if resp.StatusCode != http.StatusOK { - log.WithFields(log.Fields{"status": resp.StatusCode, "body": string(body)}).Error("Error response from compliance server") - return nil, errors.New("Error response from compliance server") - } - - var receiveResponse callback.ReceiveResponse - err = json.Unmarshal([]byte(body), &receiveResponse) - if err != nil { - return nil, errors.Wrap(err, "Cannot unmarshal receiveResponse") - } - - var authData compliance.AuthData - err = json.Unmarshal([]byte(receiveResponse.Data), &authData) - if err != nil { - return nil, errors.Wrap(err, "Cannot unmarshal authData") - } - - return &authData, nil -} - -// AdminReceivedPayments implements /admin/received-payments endpoint -func (rh *RequestHandler) AdminReceivedPayments(w http.ResponseWriter, r *http.Request) { - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - limit := 10 - - payments, err := rh.Database.GetReceivedPayments(uint64(page), uint64(limit)) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error loading ReceivedPayments") - helpers.Write(w, helpers.InternalServerError) - return - } - - encoder := json.NewEncoder(w) - err = encoder.Encode(payments) - if err != nil { - log.WithFields(log.Fields{"err": err, "payments": payments}).Error("Error encoding ReceivedPayments") - helpers.Write(w, helpers.InternalServerError) - return - } -} - -// AdminReceivedPayments implements /admin/sent-transactions endpoint -func (rh *RequestHandler) AdminSentTransactions(w http.ResponseWriter, r *http.Request) { - page, _ := strconv.Atoi(r.URL.Query().Get("page")) - limit := 10 - - transactions, err := rh.Database.GetSentTransactions(uint64(page), uint64(limit)) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error loading SentTransactions") - helpers.Write(w, helpers.InternalServerError) - return - } - - encoder := json.NewEncoder(w) - err = encoder.Encode(transactions) - if err != nil { - log.WithFields(log.Fields{"err": err, "transactions": transactions}).Error("Error encoding SentTransactions") - helpers.Write(w, helpers.InternalServerError) - return - } -} diff --git a/services/bridge/internal/handlers/request_handler_authorize.go b/services/bridge/internal/handlers/request_handler_authorize.go deleted file mode 100644 index 3e907ced90..0000000000 --- a/services/bridge/internal/handlers/request_handler_authorize.go +++ /dev/null @@ -1,84 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - log "github.com/sirupsen/logrus" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - "github.com/stellar/go/txnbuild" -) - -// Authorize implements /authorize endpoint -func (rh *RequestHandler) Authorize(w http.ResponseWriter, r *http.Request) { - request := &bridge.AuthorizeRequest{} - err := helpers.FromRequest(r, request) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InvalidParameterError) - return - } - - err = helpers.Validate(request, rh.Config.Assets, rh.Config.Accounts.IssuingAccountID) - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - var sourceAccount *string - if rh.Config.Accounts.IssuingAccountID != "" { - sourceAccount = &rh.Config.Accounts.IssuingAccountID - } - - allowTrustOp := bridge.AllowTrustOperationBody{ - Source: sourceAccount, - Authorize: true, - Trustor: request.AccountID, - AssetCode: request.AssetCode, - } - - operationBuilder := allowTrustOp.Build() - - submitResponse, err := rh.TransactionSubmitter.SubmitTransaction( - nil, - rh.Config.Accounts.AuthorizingSeed, - []txnbuild.Operation{operationBuilder}, - nil, - ) - - jsonEncoder := json.NewEncoder(w) - - if err != nil { - herr, isHorizonError := err.(*hc.Error) - if !isHorizonError { - log.WithFields(log.Fields{"err": err}).Error("Error submitting transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - w.WriteHeader(herr.Problem.Status) - err = jsonEncoder.Encode(herr.Problem) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error encoding response") - helpers.Write(w, helpers.InternalServerError) - return - } - - return - } - - err = jsonEncoder.Encode(submitResponse) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error encoding response") - helpers.Write(w, helpers.InternalServerError) - return - } -} diff --git a/services/bridge/internal/handlers/request_handler_authorize_test.go b/services/bridge/internal/handlers/request_handler_authorize_test.go deleted file mode 100644 index 9f8710a5b5..0000000000 --- a/services/bridge/internal/handlers/request_handler_authorize_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "net/url" - "strings" - "testing" - - "github.com/stellar/go/support/errors" - "github.com/stellar/go/support/http/httptest" - "github.com/stellar/go/txnbuild" - "github.com/stretchr/testify/assert" - - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/mocks" - "github.com/stellar/go/services/bridge/internal/test" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" -) - -func TestRequestHandlerAuthorize(t *testing.T) { - - mockTS := new(mocks.MockTransactionSubmitter) - - config := config.Config{ - Assets: []protocols.Asset{ - {Code: "USD", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - {Code: "EUR", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - }, - Accounts: config.Accounts{ - IssuingAccountID: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR", - // GBQXA3ABGQGTCLEVZIUTDRWWJOQD5LSAEDZAG7GMOGD2HBLWONGUVO4I - AuthorizingSeed: "SC37TBSIAYKIDQ6GTGLT2HSORLIHZQHBXVFI5P5K4Q5TSHRTRBK3UNWG", - }, - } - - requestHandler := RequestHandler{Config: &config, TransactionSubmitter: mockTS} - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.Authorize)) - defer testServer.Close() - - // Invalid Account ID - accountID := "GD3YBOYIUVLU" - assetCode := "USD" - - statusCode, response := mocks.GetResponse(testServer, url.Values{"account_id": {accountID}, "asset_code": {assetCode}}) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected := test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "AccountID" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // no asset code - accountID = "GCOGCYU77DLEVYCXDQM7F32M5PCKES6VU3Z5GURF6U6OA5LFOVTRYPOX" - assetCode = "" - - statusCode, response = mocks.GetResponse(testServer, url.Values{"account_id": {accountID}, "asset_code": {assetCode}}) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "missing_parameter", - "message": "Required parameter is missing.", - "data": { - "name": "AssetCode" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // Valid parameters - accountID = "GCOGCYU77DLEVYCXDQM7F32M5PCKES6VU3Z5GURF6U6OA5LFOVTRYPOX" - assetCode = "USD" - - allowTrustOp := bridge.AllowTrustOperationBody{ - Source: &config.Accounts.IssuingAccountID, - Authorize: true, - Trustor: accountID, - AssetCode: assetCode, - } - - operationBuilder := allowTrustOp.Build() - - // tx failure - mockTS.On( - "SubmitTransaction", - (*string)(nil), - config.Accounts.AuthorizingSeed, - []txnbuild.Operation{operationBuilder}, - nil, - ).Return(hProtocol.Transaction{}, - errors.New("Error sending transaction"), - ).Once() - - statusCode, response = mocks.GetResponse(testServer, url.Values{"account_id": {accountID}, "asset_code": {assetCode}}) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 500, statusCode) - expected = test.StringToJSONMap(`{ - "code": "internal_server_error", - "message": "Internal Server Error, please try again." - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - mockTS.AssertExpectations(t) - - var ledger int32 - ledger = 100 - expectedSubmitResponse := hProtocol.Transaction{ - Ledger: ledger, - } - - mockTS.On( - "SubmitTransaction", - (*string)(nil), - config.Accounts.AuthorizingSeed, - []txnbuild.Operation{operationBuilder}, - nil, - ).Return(expectedSubmitResponse, nil).Once() - statusCode, response = mocks.GetResponse(testServer, url.Values{"account_id": {accountID}, "asset_code": {assetCode}}) - var actualSubmitTransactionResponse hProtocol.Transaction - json.Unmarshal(response, &actualSubmitTransactionResponse) - - assert.Equal(t, 200, statusCode) - assert.Equal(t, expectedSubmitResponse, actualSubmitTransactionResponse) - mockTS.AssertExpectations(t) -} diff --git a/services/bridge/internal/handlers/request_handler_builder.go b/services/bridge/internal/handlers/request_handler_builder.go deleted file mode 100644 index 024a941e0a..0000000000 --- a/services/bridge/internal/handlers/request_handler_builder.go +++ /dev/null @@ -1,123 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "strconv" - - log "github.com/sirupsen/logrus" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - protocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - "github.com/stellar/go/txnbuild" -) - -// Builder implements /builder endpoint -func (rh *RequestHandler) Builder(w http.ResponseWriter, r *http.Request) { - var request bridge.BuilderRequest - var sequenceNumber uint64 - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&request) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error decoding request") - helpers.Write(w, helpers.NewInvalidParameterError("", "Request body is not a valid JSON")) - return - } - - err = request.Process() - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - if request.SequenceNumber == "" { - accountRequest := hc.AccountRequest{AccountID: request.Source} - var accountResponse protocol.Account - accountResponse, err = rh.Horizon.AccountDetail(accountRequest) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error when loading account") - helpers.Write(w, helpers.InternalServerError) - return - } - sequenceNumber, err = strconv.ParseUint(accountResponse.Sequence, 10, 64) - } else { - sequenceNumber, err = strconv.ParseUint(request.SequenceNumber, 10, 64) - if err == nil { - // decrement sequence number when it is provided because txnbuild will autoincrement - // to do: remove in txnbuild v2.* - sequenceNumber = sequenceNumber - 1 - } - } - - if err != nil { - errorResponse := helpers.NewInvalidParameterError("sequence_number", "Sequence number must be a number") - helpers.Write(w, errorResponse) - return - } - - var txOps []txnbuild.Operation - for _, operation := range request.Operations { - txOps = append(txOps, operation.Body.Build()) - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{AccountID: request.Source, Sequence: int64(sequenceNumber)}, - IncrementSequenceNum: true, - Operations: txOps, - BaseFee: txnbuild.MinBaseFee, - Timebounds: txnbuild.NewInfiniteTimeout(), // Use a real timeout in production! - }, - ) - if err != nil { - log.WithFields(log.Fields{"err": err, "request": request}).Error("TransactionBuilder returned error") - helpers.Write(w, helpers.InternalServerError) - return - } - - for _, s := range request.Signers { - var kp keypair.KP - kp, err = keypair.Parse(s) - if err != nil { - log.WithFields(log.Fields{"err": err, "request": request}).Error("Error converting signers to keypairs") - helpers.Write(w, helpers.InternalServerError) - return - } - - tx, err = tx.Sign(rh.Config.NetworkPassphrase, kp.(*keypair.Full)) - if err != nil { - log.WithFields(log.Fields{"err": err, "request": request}).Error("Error signing transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - } - - txeBase64, err := tx.Base64() - if err != nil { - log.WithFields(log.Fields{"err": err, "request": request}).Error("Error encoding transaction envelope") - helpers.Write(w, helpers.InternalServerError) - return - } - - helpers.Write(w, &bridge.BuilderResponse{TransactionEnvelope: txeBase64}) -} diff --git a/services/bridge/internal/handlers/request_handler_builder_test.go b/services/bridge/internal/handlers/request_handler_builder_test.go deleted file mode 100644 index 984132ea0c..0000000000 --- a/services/bridge/internal/handlers/request_handler_builder_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package handlers - -import ( - "net/http" - "strings" - "testing" - - hc "github.com/stellar/go/clients/horizonclient" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/bridge/internal/config" - - "github.com/stellar/go/services/bridge/internal/mocks" - "github.com/stellar/go/services/bridge/internal/test" - "github.com/stellar/go/support/http/httptest" - "github.com/stretchr/testify/assert" -) - -func TestRequestHandlerBuilder(t *testing.T) { - c := &config.Config{NetworkPassphrase: "Test SDF Network ; September 2015"} - - mockHorizon := new(hc.MockClient) - mockHTTPClient := new(mocks.MockHTTPClient) - mockTS := new(mocks.MockTransactionSubmitter) - mockFederationResolver := new(mocks.MockFederationResolver) - mockStellartomlResolver := new(mocks.MockStellartomlResolver) - - requestHandler := RequestHandler{ - Config: c, - Client: mockHTTPClient, - Horizon: mockHorizon, - TransactionSubmitter: mockTS, - FederationResolver: mockFederationResolver, - StellarTomlResolver: mockStellartomlResolver, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.Builder)) - defer testServer.Close() - - // When no sequence number IS NOT supplied - data := test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "", - "operations": [ - { - "type": "create_account", - "body": { - "destination": "GCOEGO43PFSLE4K7WRZQNRO3PIOTRLKRASP32W7DSPBF65XFT4V6PSV3", - "starting_balance": "50" - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // Loading sequence number - accountRequest := hc.AccountRequest{AccountID: "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5"} - - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return( - hProtocol.Account{ - Sequence: "123", - }, - nil, - ).Once() - - // it should return the correct XDR - statusCode, response := mocks.JSONGetResponse(testServer, data) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB8AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAB3NZQAAAAAAAAAAAZ%2BNtPwAAABAXJ4I9DwPZKt7yO0j8IsIsc6VLUJz%2FyZ3LK%2F%2F%2Bxpxf6Mbbl%2B7cFq7wG776h7VLmDTBrSEQOydKuR0Yup4gFqYAQ%3D%3D&type=TransactionEnvelope&network=test - expected := test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB8AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAB3NZQAAAAAAAAAAAZ+NtPwAAABAXJ4I9DwPZKt7yO0j8IsIsc6VLUJz/yZ3LK//+xpxf6Mbbl+7cFq7wG776h7VLmDTBrSEQOydKuR0Yup4gFqYAQ==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When sequence number IS supplied - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "create_account", - "body": { - "destination": "GCOEGO43PFSLE4K7WRZQNRO3PIOTRLKRASP32W7DSPBF65XFT4V6PSV3", - "starting_balance": "50" - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR, sequence number should not be incremented - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAB3NZQAAAAAAAAAAAZ%2BNtPwAAABAN836LQKoUHzAKLTkizVKb9PsFdf73eNOSRKKGd%2BzAB9GrnlsKDLbegtt9eqvYjQ4AHzbeqJBIGX%2FHQFXCadAAw%3D%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAB3NZQAAAAAAAAAAAZ+NtPwAAABAN836LQKoUHzAKLTkizVKb9PsFdf73eNOSRKKGd+zAB9GrnlsKDLbegtt9eqvYjQ4AHzbeqJBIGX/HQFXCadAAw==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // build payment - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "payment", - "body": { - "destination": "GCOEGO43PFSLE4K7WRZQNRO3PIOTRLKRASP32W7DSPBF65XFT4V6PSV3", - "amount": "100", - "asset": { - "code": "USD", - "issuer": "GACETOPHMOLSZLG5IQ3D6KQDKCAAYUYTTQHIEY6IGZE4VOBDD2YY6YAO" - } - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"]}`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAVVTRAAAAAAABEm552OXLKzdRDY%2FKgNQgAxTE5wOgmPINknKuCMesY8AAAAAO5rKAAAAAAAAAAABn420%2FAAAAEBckKXSMZ9M2sZqbv53XTGw0Mv91MntHbQpn%2FV0TtoVGHBWMLIJ8ePG7E0%2B7Dc06g%2BAUR%2FoTWaoI6WWCe5SKMAL&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAJxDO5t5ZLJxX7RzBsXbeh04rVEEn71b45PCX3blnyvnAAAAAVVTRAAAAAAABEm552OXLKzdRDY/KgNQgAxTE5wOgmPINknKuCMesY8AAAAAO5rKAAAAAAAAAAABn420/AAAAEBckKXSMZ9M2sZqbv53XTGw0Mv91MntHbQpn/V0TtoVGHBWMLIJ8ePG7E0+7Dc06g+AUR/oTWaoI6WWCe5SKMAL" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Path Payment - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "path_payment", - "body": { - "source": "GBYLUAJBHGZMVAYCALM4ZRTAOY74NTSSULG42VKVY2EOWS5X2HFBB2VL", - "destination": "GCOEGO43PFSLE4K7WRZQNRO3PIOTRLKRASP32W7DSPBF65XFT4V6PSV3", - "destination_amount": "500", - "destination_asset": { - "code": "EUR", - "issuer": "GDOJMKTDLGGLROSSM5BV5MXIAQ3JZHASQFUV55WBJ45AFOUXSVVFGPTJ" - }, - "send_max": "100", - "send_asset": { - "code": "USD", - "issuer": "GACETOPHMOLSZLG5IQ3D6KQDKCAAYUYTTQHIEY6IGZE4VOBDD2YY6YAO" - }, - "path": [ - { - "code": "ABCDEFG", - "issuer": "GD4RIHH2HWB4MPJN72G2VGLRPUXDODFNQG6DVU47HMSSSF3RIQ4UXALD" - }, - {} - ] - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAcLoBITmyyoMCAtnMxmB2P8bOUqLNzVVVxojrS7fRyhAAAAACAAAAAVVTRAAAAAAABEm552OXLKzdRDY%2FKgNQgAxTE5wOgmPINknKuCMesY8AAAAAO5rKAAAAAACcQzubeWSycV%2B0cwbF23odOK1RBJ%2B9W%2BOTwl925Z8r5wAAAAFFVVIAAAAAANyWKmNZjLi6UmdDXrLoBDacnBKBaV72wU86ArqXlWpTAAAAASoF8gAAAAACAAAAAkFCQ0RFRkcAAAAAAAAAAAD5FBz6PYPGPS3%2BjaqZcX0uNwytgbw60587JSkXcUQ5SwAAAAAAAAAAAAAAAZ%2BNtPwAAABA1z8yIaQPklvS08JcoyY6puiTzpG9KyCiJPRlLYUYp04xEVsktvBjVZTwy%2Bbt2JvCo03iO0e3xBS7IVVIMwW0Bg%3D%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAcLoBITmyyoMCAtnMxmB2P8bOUqLNzVVVxojrS7fRyhAAAAACAAAAAVVTRAAAAAAABEm552OXLKzdRDY/KgNQgAxTE5wOgmPINknKuCMesY8AAAAAO5rKAAAAAACcQzubeWSycV+0cwbF23odOK1RBJ+9W+OTwl925Z8r5wAAAAFFVVIAAAAAANyWKmNZjLi6UmdDXrLoBDacnBKBaV72wU86ArqXlWpTAAAAASoF8gAAAAACAAAAAkFCQ0RFRkcAAAAAAAAAAAD5FBz6PYPGPS3+jaqZcX0uNwytgbw60587JSkXcUQ5SwAAAAAAAAAAAAAAAZ+NtPwAAABA1z8yIaQPklvS08JcoyY6puiTzpG9KyCiJPRlLYUYp04xEVsktvBjVZTwy+bt2JvCo03iO0e3xBS7IVVIMwW0Bg==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Set Options - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "set_options", - "body": { - "inflation_dest": "GBMPZVOMJ67WQBTBCVURDKTGL4557272EGQMAJCXPSMLOE63XPLL6SVA", - "set_flags": [1, 2], - "clear_flags": [4], - "master_weight": 100, - "low_threshold": 1, - "medium_threshold": 2, - "high_threshold": 3, - "home_domain": "stellar.org", - "signer": { - "public_key": "GA6VMJJQM2QBPPIXK2UVTAOS4XSSSAKSCOGFQE55IMRBQR65GIVDTTQV", - "weight": 5 - } - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAFAAAAAQAAAABY%2FNXMT79oBmEVaRGqZl873%2Bv6IaDAJFd8mLcT27vWvwAAAAEAAAAEAAAAAQAAAAMAAAABAAAAZAAAAAEAAAABAAAAAQAAAAIAAAABAAAAAwAAAAEAAAALc3RlbGxhci5vcmcAAAAAAQAAAAA9ViUwZqAXvRdWqVmB0uXlKQFSE4xYE71DIhhH3TIqOQAAAAUAAAAAAAAAAZ%2BNtPwAAABAHYQbLjs%2FAAekVMuuU6cxLxQYG7m396Im%2BSNiSsjSyUdgjjSKt7xzupvgudjBHM1t2akOmx1OCzmlsWCSEWzoCQ%3D%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAFAAAAAQAAAABY/NXMT79oBmEVaRGqZl873+v6IaDAJFd8mLcT27vWvwAAAAEAAAAEAAAAAQAAAAMAAAABAAAAZAAAAAEAAAABAAAAAQAAAAIAAAABAAAAAwAAAAEAAAALc3RlbGxhci5vcmcAAAAAAQAAAAA9ViUwZqAXvRdWqVmB0uXlKQFSE4xYE71DIhhH3TIqOQAAAAUAAAAAAAAAAZ+NtPwAAABAHYQbLjs/AAekVMuuU6cxLxQYG7m396Im+SNiSsjSyUdgjjSKt7xzupvgudjBHM1t2akOmx1OCzmlsWCSEWzoCQ==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Change Trust - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "change_trust", - "body": { - "asset": { - "code": "USD", - "issuer": "GCHGRVNTXAV3OXNMCSA63BUCD6AZZX6PN2542QB6GIVTXGHQ65XS35DS" - } - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAGAAAAAVVTRAAAAAAAjmjVs7grt12sFIHthoIfgZzfz267zUA%2BMis7mPD3by1%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwAAAAAAAAABn420%2FAAAAEAr4jYveod1stUGiW9Uy99mDz5gjCJp%2FNPTu3P0uVRLEGOAcM9GyEMCvm2VnK4HAtSZiCxrCmcZGTqSd38zUBgC&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAGAAAAAVVTRAAAAAAAjmjVs7grt12sFIHthoIfgZzfz267zUA+Mis7mPD3by1//////////wAAAAAAAAABn420/AAAAEAr4jYveod1stUGiW9Uy99mDz5gjCJp/NPTu3P0uVRLEGOAcM9GyEMCvm2VnK4HAtSZiCxrCmcZGTqSd38zUBgC" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Allow Trust - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "allow_trust", - "body": { - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "asset_code": "USDUSD", - "trustor": "GBLH67TQHRNRLERQEIQJDNBV2DSWPHAPP43MBIF7DVKA7X55APUNS4LL", - "authorize": true - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAbJJLdnKV98VpVmpkPqU0JSMKLMfPsws%2FCQ3szGHhc3kAAAAHAAAAAFZ%2FfnA8WxWSMCIgkbQ10OVnnA9%2FNsCgvx1UD9%2B9A%2BjZAAAAAlVTRFVTRAAAAAAAAAAAAAEAAAAAAAAAAZ%2BNtPwAAABAWROxcSLBvH04%2BZoTD%2BYv47Xv%2Bympi9pC1pYW%2Bh9JKUI9yc49FQLWM3svhyOxF%2BmyCQvt%2Fkb7yrnlPVLlWSvACA%3D%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAbJJLdnKV98VpVmpkPqU0JSMKLMfPsws/CQ3szGHhc3kAAAAHAAAAAFZ/fnA8WxWSMCIgkbQ10OVnnA9/NsCgvx1UD9+9A+jZAAAAAlVTRFVTRAAAAAAAAAAAAAEAAAAAAAAAAZ+NtPwAAABAWROxcSLBvH04+ZoTD+Yv47Xv+ympi9pC1pYW+h9JKUI9yc49FQLWM3svhyOxF+myCQvt/kb7yrnlPVLlWSvACA==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Account Merge - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "account_merge", - "body": { - "destination": "GBLH67TQHRNRLERQEIQJDNBV2DSWPHAPP43MBIF7DVKA7X55APUNS4LL" - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - // https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAIAAAAAFZ%2FfnA8WxWSMCIgkbQ10OVnnA9%2FNsCgvx1UD9%2B9A%2BjZAAAAAAAAAAGfjbT8AAAAQKC1XKE3ThTPMsc%2Fda80CqkmesOhPa2lrsLLszR2VzbUF%2BsSJSPpq39CdsQj0rRPY61hakf5hv319NpsCmqYNAI%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAIAAAAAFZ/fnA8WxWSMCIgkbQ10OVnnA9/NsCgvx1UD9+9A+jZAAAAAAAAAAGfjbT8AAAAQKC1XKE3ThTPMsc/da80CqkmesOhPa2lrsLLszR2VzbUF+sSJSPpq39CdsQj0rRPY61hakf5hv319NpsCmqYNAI=" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // Build Inflation - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "inflation", - "body": {} - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - //https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAGfjbT8AAAAQDukGibnqm%2B%2FGQyn0xZH4a%2FyZlUNAolyrhXzsmVeCmxs3ss24RwEST2lSqrwBLnTYbpLrHPlFT2Ye14apwJ8gA4%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAGfjbT8AAAAQDukGibnqm+/GQyn0xZH4a/yZlUNAolyrhXzsmVeCmxs3ss24RwEST2lSqrwBLnTYbpLrHPlFT2Ye14apwJ8gA4=" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - //Build Manage Data - data = test.StringToJSONMap(`{ - "source": "GBWJES3WOKK7PRLJKZVGIPVFGQSSGCRMY7H3GCZ7BEG6ZTDB4FZXTPJ5", - "sequence_number": "123", - "operations": [ - { - "type": "manage_data", - "body": { - "name": "test_data", - "data": "AQIDBAUG" - } - } - ], - "signers": ["SABY7FRMMJWPBTKQQ2ZN43AUJQ3Z2ZAK36VYSG2SPE2ABNQXA66H5E5G"] - }`, - ) - - // it should return the correct XDR - statusCode, response = mocks.JSONGetResponse(testServer, data) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - - //https://www.stellar.org/laboratory/#xdr-viewer?input=AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAKAAAACXRlc3RfZGF0YQAAAAAAAAEAAAAGAQIDBAUGAAAAAAAAAAAAAZ%2BNtPwAAABAHv%2BHVBeU%2B2Cz6xpleNt3sKt%2BK%2FRCEbp349iBKnXbBz3fu4vy0UiZPMDpBJF3weCryacTXP0JxH47eQUmwCm1AA%3D%3D&type=TransactionEnvelope&network=test - expected = test.StringToJSONMap(`{ - "transaction_envelope": "AAAAAGySS3ZylffFaVZqZD6lNCUjCizHz7MLPwkN7Mxh4XN5AAAAZAAAAAAAAAB7AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAKAAAACXRlc3RfZGF0YQAAAAAAAAEAAAAGAQIDBAUGAAAAAAAAAAAAAZ+NtPwAAABAHv+HVBeU+2Cz6xpleNt3sKt+K/RCEbp349iBKnXbBz3fu4vy0UiZPMDpBJF3weCryacTXP0JxH47eQUmwCm1AA==" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - -} diff --git a/services/bridge/internal/handlers/request_handler_create_keypair.go b/services/bridge/internal/handlers/request_handler_create_keypair.go deleted file mode 100644 index c9c0bef536..0000000000 --- a/services/bridge/internal/handlers/request_handler_create_keypair.go +++ /dev/null @@ -1,34 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - - log "github.com/sirupsen/logrus" - - "github.com/stellar/go/keypair" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -// KeyPair struct contains key pair public and private key -type KeyPair struct { - PublicKey string `json:"public_key"` - PrivateKey string `json:"private_key"` -} - -// CreateKeypair implements /create-keypair endpoint -func (rh *RequestHandler) CreateKeypair(w http.ResponseWriter, r *http.Request) { - kp, err := keypair.Random() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error generating random keypair") - helpers.Write(w, helpers.InternalServerError) - } - - response, err := json.Marshal(KeyPair{kp.Address(), kp.Seed()}) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error marshalling random keypair") - helpers.Write(w, helpers.InternalServerError) - } - - w.Write(response) -} diff --git a/services/bridge/internal/handlers/request_handler_payment.go b/services/bridge/internal/handlers/request_handler_payment.go deleted file mode 100644 index 9e9897ff06..0000000000 --- a/services/bridge/internal/handlers/request_handler_payment.go +++ /dev/null @@ -1,331 +0,0 @@ -package handlers - -import ( - "encoding/hex" - "encoding/json" - "io/ioutil" - "net/http" - "strconv" - - log "github.com/sirupsen/logrus" - - "github.com/stellar/go/address" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/txnbuild" - - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/protocols/federation" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/xdr" -) - -// Payment implements /payment endpoint -func (rh *RequestHandler) Payment(w http.ResponseWriter, r *http.Request) { - request := &bridge.PaymentRequest{} - err := helpers.FromRequest(r, request) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InvalidParameterError) - return - } - - err = helpers.Validate(request, rh.Config.Accounts.BaseSeed) - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - if request.Source == "" { - request.Source = rh.Config.Accounts.BaseSeed - } - - // Will use compliance if compliance server is connected and: - // * User passed extra memo OR - // * User explicitly wants to use compliance protocol - if rh.Config.Compliance != "" && - (request.ExtraMemo != "" || (request.ExtraMemo == "" && request.UseCompliance)) { - rh.complianceProtocolPayment(w, request) - } else { - rh.standardPayment(w, request) - } -} - -func (rh *RequestHandler) complianceProtocolPayment(w http.ResponseWriter, request *bridge.PaymentRequest) { - var paymentID *string - if request.ID != "" { - paymentID = &request.ID - } - - // Compliance server part - sendRequest := request.ToComplianceSendRequest() - - resp, err := rh.Client.PostForm( - rh.Config.Compliance+"/send", - helpers.ToValues(sendRequest), - ) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error sending request to compliance server") - helpers.Write(w, helpers.InternalServerError) - return - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Error("Error reading compliance server response") - helpers.Write(w, helpers.InternalServerError) - return - } - - if resp.StatusCode != 200 { - log.WithFields(log.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from compliance server") - helpers.Write(w, helpers.InternalServerError) - return - } - - var callbackSendResponse callback.SendResponse - err = json.Unmarshal(body, &callbackSendResponse) - if err != nil { - log.Error("Error unmarshalling from compliance server") - helpers.Write(w, helpers.InternalServerError) - return - } - - if callbackSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusPending || - callbackSendResponse.AuthResponse.TxStatus == compliance.AuthStatusPending { - log.WithFields(log.Fields{"response": callbackSendResponse}).Info("Compliance response pending") - helpers.Write(w, bridge.NewPaymentPendingError(callbackSendResponse.AuthResponse.Pending)) - return - } - - if callbackSendResponse.AuthResponse.InfoStatus == compliance.AuthStatusDenied || - callbackSendResponse.AuthResponse.TxStatus == compliance.AuthStatusDenied { - log.WithFields(log.Fields{"response": callbackSendResponse}).Info("Compliance response denied") - helpers.Write(w, bridge.PaymentDenied) - return - } - - var tx xdr.Transaction - err = xdr.SafeUnmarshalBase64(callbackSendResponse.TransactionXdr, &tx) - if err != nil { - log.Error("Error unmarshalling transaction returned by compliance server") - helpers.Write(w, helpers.InternalServerError) - return - } - - submitResponse, err := rh.TransactionSubmitter.SignAndSubmitRawTransaction(paymentID, request.Source, &tx) - rh.handleTransactionSubmitResponse(w, submitResponse, err) -} - -func (rh *RequestHandler) standardPayment(w http.ResponseWriter, request *bridge.PaymentRequest) { - var paymentID *string - - if request.ID != "" { - sentTransaction, err := rh.Database.GetSentTransactionByPaymentID(request.ID) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error getting sent transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - if sentTransaction == nil { - paymentID = &request.ID - } else { - log.WithFields(log.Fields{"paymentID": request.ID, "tx": sentTransaction.EnvelopeXdr}).Info("Transaction with given ID already exists, resubmitting...") - submitResponse, err := rh.Horizon.SubmitTransactionXDR(sentTransaction.EnvelopeXdr) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("Error submitting transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - rh.handleTransactionSubmitResponse(w, submitResponse, err) - return - } - } - - destinationObject := &federation.NameResponse{} - var err error - - if request.ForwardDestination == nil { - _, _, err = address.Split(request.Destination) - if err != nil { - destinationObject.AccountID = request.Destination - } else { - destinationObject, err = rh.FederationResolver.LookupByAddress(request.Destination) - if err != nil { - log.WithFields(log.Fields{"destination": request.Destination, "err": err}).Print("Cannot resolve address") - helpers.Write(w, bridge.PaymentCannotResolveDestination) - return - } - } - } else { - destinationObject, err = rh.FederationResolver.ForwardRequest(request.ForwardDestination.Domain, request.ForwardDestination.Fields) - if err != nil { - log.WithFields(log.Fields{"destination": request.Destination, "err": err}).Print("Cannot resolve address") - helpers.Write(w, bridge.PaymentCannotResolveDestination) - return - } - } - - if !shared.IsValidAccountID(destinationObject.AccountID) { - log.WithFields(log.Fields{"AccountId": destinationObject.AccountID}).Print("Invalid AccountId in destination") - helpers.Write(w, helpers.NewInvalidParameterError("destination", "Destination public key must start with `G`.")) - return - } - - var rSource *string - if request.Source != "" { - var kp keypair.KP - kp, err = keypair.Parse(request.Source) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("Unable to convert seed to keypair") - helpers.Write(w, helpers.NewInvalidParameterError("source", "Source must be a valid secret seed.")) - } - kpAddress := kp.Address() - rSource = &kpAddress - } - - var operationBuilder txnbuild.Operation - - // Check if destination account exist - accountRequest := hc.AccountRequest{AccountID: destinationObject.AccountID} - _, err = rh.Horizon.AccountDetail(accountRequest) - if err != nil { - log.WithFields(log.Fields{"error": err}).Error("Error loading destination account") - - // if pathpayment or custom asset - if request.SendMax != "" || (request.AssetCode != "" && request.AssetIssuer != "") { - // return error instead of creating account - log.WithFields(log.Fields{"destination": request.Destination}).Error("can not send custom asset or path payment to inactive destination") - helpers.Write(w, helpers.NewInvalidParameterError("destination", "Can not send custom asset or path payment to inactive destination.")) - return - } - - paymentOp := bridge.CreateAccountOperationBody{ - Source: rSource, - Destination: request.Destination, - StartingBalance: request.Amount, - } - operationBuilder = paymentOp.Build() - } else { - // check if Path payment - if request.SendMax != "" { - paymentOp := bridge.PathPaymentOperationBody{ - Source: rSource, - SendMax: request.SendMax, - SendAsset: protocols.Asset{Code: request.SendAssetCode, Issuer: request.SendAssetIssuer}, - Destination: request.Destination, - DestinationAmount: request.Amount, - DestinationAsset: protocols.Asset{Code: request.AssetCode, Issuer: request.AssetIssuer}, - Path: request.Path, - } - - operationBuilder = paymentOp.Build() - } else { - paymentOp := bridge.PaymentOperationBody{ - Source: rSource, - Destination: request.Destination, - Amount: request.Amount, - Asset: protocols.Asset{Code: request.AssetCode, Issuer: request.AssetIssuer}, - } - - operationBuilder = paymentOp.Build() - } - } - - memoType := request.MemoType - memo := request.Memo - - if destinationObject.MemoType != "" { - if request.MemoType != "" { - log.Print("Memo given in request but federation returned memo fields.") - helpers.Write(w, bridge.PaymentCannotUseMemo) - return - } - - memoType = destinationObject.MemoType - memo = destinationObject.Memo.Value - } - - var txMemo txnbuild.Memo - - switch { - case memoType == "": - break - case memoType == "id": - var id uint64 - id, err = strconv.ParseUint(memo, 10, 64) - if err != nil { - log.WithFields(log.Fields{"memo": memo}).Print("Cannot convert memo_id value to uint64") - helpers.Write(w, helpers.NewInvalidParameterError("memo", "Memo.id must be a number")) - return - } - txMemo = txnbuild.MemoID(id) - case memoType == "text": - txMemo = txnbuild.MemoText(memo) - case memoType == "hash": - var memoBytes []byte - memoBytes, err = hex.DecodeString(memo) - if err != nil || len(memoBytes) != 32 { - log.WithFields(log.Fields{"memo": memo}).Print("Cannot decode hash memo value") - helpers.Write(w, helpers.NewInvalidParameterError("memo", "Memo.hash must be 32 bytes and hex encoded.")) - return - } - var b32 [32]byte - copy(b32[:], memoBytes[0:32]) - txMemo = txnbuild.MemoHash(b32) - default: - log.Print("Not supported memo type: ", memoType) - helpers.Write(w, helpers.NewInvalidParameterError("memo", "Memo type not supported")) - return - } - - submitResponse, err := rh.TransactionSubmitter.SubmitTransaction(paymentID, request.Source, []txnbuild.Operation{operationBuilder}, txMemo) - rh.handleTransactionSubmitResponse(w, submitResponse, err) -} - -func (rh *RequestHandler) handleTransactionSubmitResponse(w http.ResponseWriter, submitResponse hProtocol.Transaction, err error) { - jsonEncoder := json.NewEncoder(w) - - if err != nil { - herr, isHorizonError := err.(*hc.Error) - if !isHorizonError { - log.WithFields(log.Fields{"err": err}).Error("Error submitting transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - w.WriteHeader(herr.Problem.Status) - err = jsonEncoder.Encode(herr.Problem) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error encoding response") - helpers.Write(w, helpers.InternalServerError) - return - } - - return - } - - err = jsonEncoder.Encode(submitResponse) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error encoding response") - helpers.Write(w, helpers.InternalServerError) - return - } -} diff --git a/services/bridge/internal/handlers/request_handler_payment_test.go b/services/bridge/internal/handlers/request_handler_payment_test.go deleted file mode 100644 index 0751c7861b..0000000000 --- a/services/bridge/internal/handlers/request_handler_payment_test.go +++ /dev/null @@ -1,494 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "net/url" - "strings" - "testing" - - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/protocols/federation" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/mocks" - "github.com/stellar/go/services/bridge/internal/test" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/support/http/httptest" - "github.com/stellar/go/txnbuild" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -var phconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Compliance: "http://compliance", - Assets: []protocols.Asset{ - {Code: "USD", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - {Code: "EUR", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - }, - Accounts: config.Accounts{ - // GAHA6GRCLCCN7XE2NEEUDSIVOFBOQ6GLSYXVLYCJXJKLPMDR5XB5XZZJ - BaseSeed: "SBKKWO3ZVDDEHDJILGHPHCJCFD2GNUAYIUDMRAS326HLUEQ7ZFXWIGQK", - }, -} -var mockHorizon = new(hc.MockClient) -var mockDatabase = new(mocks.MockDatabase) -var mockHTTPClient = new(mocks.MockHTTPClient) -var mockTransactionSubmitter = new(mocks.MockTransactionSubmitter) -var mockFederationResolver = new(mocks.MockFederationResolver) -var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - -func TestRequestHandlerPaymentInvalidParameter(t *testing.T) { - requestHandler := RequestHandler{ - Config: phconfig, - Client: mockHTTPClient, - Horizon: mockHorizon, - Database: mockDatabase, - TransactionSubmitter: mockTransactionSubmitter, - FederationResolver: mockFederationResolver, - StellarTomlResolver: mockStellartomlResolver, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.Payment)) - defer testServer.Close() - - // When source is invalid it should return error - params := url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX43"}, - "destination": {"GBABZMS7MEDWKWSHOMUKAWGIOE5UA4XLVPUHRHVMUW2DUVEZXLH5OIET"}, - "amount": {"20.0"}, - } - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected := test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "Source" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When destination is invalid it should return error - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"GD3YBOYIUVLU"}, - "amount": {"20.0"}, - } - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "Destination" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When amount is invalid it should return error - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"GBABZMS7MEDWKWSHOMUKAWGIOE5UA4XLVPUHRHVMUW2DUVEZXLH5OIET"}, - } - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "missing_parameter", - "message": "Required parameter is missing.", - "data": { - "name": "Amount" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When asset issuer is invalid it should return error - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - "asset_code": {"USD"}, - "asset_issuer": {"GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOX"}, - "amount": {"100.0"}, - } - - mockFederationResolver.On( - "LookupByAddress", - "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632", - ).Return( - &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "AssetIssuer" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When asset code is invalid it should return error - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - "asset_code": {"USD01234567890"}, - "asset_issuer": {"GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - "amount": {"100.0"}, - } - - mockFederationResolver.On( - "LookupByAddress", - "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632", - ).Return( - &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - nil, - ).Once() - - // Should return an error - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "AssetCode" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - -} - -func TestRequestHandlerPaymentErrorResponse(t *testing.T) { - requestHandler := RequestHandler{ - Config: phconfig, - Client: mockHTTPClient, - Horizon: mockHorizon, - Database: mockDatabase, - TransactionSubmitter: mockTransactionSubmitter, - FederationResolver: mockFederationResolver, - StellarTomlResolver: mockStellartomlResolver, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.Payment)) - defer testServer.Close() - - // When federation response is an error it should return error - params := url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"bob*stellar.org"}, - "amount": {"20.0"}, - } - - mockFederationResolver.On( - "LookupByAddress", - "bob*stellar.org", - ).Return( - &federation.NameResponse{}, - errors.New("stellar.toml response status code indicates error"), - ).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected := test.StringToJSONMap(`{ - "code": "cannot_resolve_destination", - "message": "Cannot resolve federated Stellar address." - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When using foward destination with federation error. It should return error - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "forward_destination[domain]": {"stellar.org"}, - "forward_destination[fields][federation_type]": {"bank_account"}, - "forward_destination[fields][swift]": {"BOPBPHMM"}, - "forward_destination[fields][acct]": {"2382376"}, - "amount": {"20.0"}, - } - - mockFederationResolver.On( - "ForwardRequest", - "stellar.org", - url.Values{ - "federation_type": {"bank_account"}, - "swift": {"BOPBPHMM"}, - "acct": {"2382376"}, - }, - ).Return( - &federation.NameResponse{}, - errors.New("stellar.toml response status code indicates error"), - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "cannot_resolve_destination", - "message": "Cannot resolve federated Stellar address." - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - -} - -func TestRequestHandlerPaymentSuccessResponse(t *testing.T) { - requestHandler := RequestHandler{ - Config: phconfig, - Client: mockHTTPClient, - Horizon: mockHorizon, - Database: mockDatabase, - TransactionSubmitter: mockTransactionSubmitter, - FederationResolver: mockFederationResolver, - StellarTomlResolver: mockStellartomlResolver, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.Payment)) - defer testServer.Close() - - // When destination is public key it should return success - params := url.Values{ - // GBKGH7QZVCZ2ZA5OUGZSTHFNXTBHL3MPCKSCBJUAQODGPMWP7OMMRKDW - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"GAPCT362RATBUJ37RN2MOKQIZLHSJMO33MMCSRUXTTHIGVDYWOFG5HDS"}, - "amount": {"20.0"}, - } - - accountRequest := hc.AccountRequest{AccountID: "GAPCT362RATBUJ37RN2MOKQIZLHSJMO33MMCSRUXTTHIGVDYWOFG5HDS"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - var ledger int32 - ledger = 1988728 - horizonResponse := hProtocol.Transaction{ - Hash: "6a0049b44e0d0341bd52f131c74383e6ccd2b74b92c829c990994d24bbfcfa7a", - Ledger: ledger, - } - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42", - mock.AnythingOfType("[]txnbuild.Operation"), - nil, - ).Return(horizonResponse, nil).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - var parsedResponse hProtocol.Transaction - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) - - // When destination is a stellar address it should return success - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "destination": {"bob*stellar.org"}, - "amount": {"20.0"}, - } - - mockFederationResolver.On( - "LookupByAddress", - "bob*stellar.org", - ).Return( - &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - nil, - ).Once() - - accountRequest = hc.AccountRequest{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42", - mock.AnythingOfType("[]txnbuild.Operation"), - nil, - ).Return(horizonResponse, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) - - // When federation response has memo it should return success - mockFederationResolver.On( - "LookupByAddress", - "bob*stellar.org", - ).Return( - &federation.NameResponse{ - AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632", - MemoType: "text", - Memo: federation.Memo{Value: "125"}, - }, - nil, - ).Once() - - accountRequest = hc.AccountRequest{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - ledger = 1988728 - horizonResponse = hProtocol.Transaction{ - Hash: "ad71fc31bfae25b0bd14add4cc5306661edf84cdd73f1353d2906363899167e1", - Ledger: ledger, - } - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42", - mock.AnythingOfType("[]txnbuild.Operation"), - txnbuild.MemoText("125"), - ).Return(horizonResponse, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) - - // When using foward destination with memo. It should return success - params = url.Values{ - "source": {"SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42"}, - "forward_destination[domain]": {"stellar.org"}, - "forward_destination[fields][federation_type]": {"bank_account"}, - "forward_destination[fields][swift]": {"BOPBPHMM"}, - "forward_destination[fields][acct]": {"2382376"}, - "amount": {"20.0"}, - } - - mockFederationResolver.On( - "ForwardRequest", - "stellar.org", - url.Values{ - "federation_type": {"bank_account"}, - "swift": {"BOPBPHMM"}, - "acct": {"2382376"}, - }, - ).Return( - &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632", - MemoType: "text", - Memo: federation.Memo{Value: "125"}}, - nil, - ).Once() - - accountRequest = hc.AccountRequest{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - ledger = 1988728 - horizonResponse = hProtocol.Transaction{ - Hash: "ad71fc31bfae25b0bd14add4cc5306661edf84cdd73f1353d2906363899167e1", - Ledger: ledger, - } - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42", - mock.AnythingOfType("[]txnbuild.Operation"), - txnbuild.MemoText("125"), - ).Return(horizonResponse, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) - - // When using foward destination without memo. It should return success - mockFederationResolver.On( - "ForwardRequest", - "stellar.org", - url.Values{ - "federation_type": {"bank_account"}, - "swift": {"BOPBPHMM"}, - "acct": {"2382376"}, - }, - ).Return( - &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - nil, - ).Once() - - accountRequest = hc.AccountRequest{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - ledger = 1988728 - horizonResponse = hProtocol.Transaction{ - Hash: "6a0049b44e0d0341bd52f131c74383e6ccd2b74b92c829c990994d24bbfcfa7a", - Ledger: ledger, - } - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SDRAS7XIQNX25UDCCX725R4EYGBFYGJE4HJ2A3DFCWJIHMRSMS7CXX42", - mock.AnythingOfType("[]txnbuild.Operation"), - nil, - ).Return(horizonResponse, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) - - // When no source is specified, it should use the base seed - params = url.Values{ - "destination": {"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - "amount": {"20"}, - "asset_code": {"USD"}, - "asset_issuer": {"GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - } - - // mockFederationResolver.On( - // "LookupByAddress", - // "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632", - // ).Return( - // &federation.NameResponse{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"}, - // nil, - // ).Once() - - accountRequest = hc.AccountRequest{AccountID: "GDSIKW43UA6JTOA47WVEBCZ4MYC74M3GNKNXTVDXFHXYYTNO5GGVN632"} - mockHorizon.On( - "AccountDetail", - accountRequest, - ).Return(hProtocol.Account{}, nil).Once() - - ledger = 1988728 - horizonResponse = hProtocol.Transaction{ - Hash: "ad71fc31bfae25b0bd14add4cc5306661edf84cdd73f1353d2906363899167e1", - Ledger: ledger, - } - - mockTransactionSubmitter.On( - "SubmitTransaction", - mock.AnythingOfType("*string"), - "SBKKWO3ZVDDEHDJILGHPHCJCFD2GNUAYIUDMRAS326HLUEQ7ZFXWIGQK", - mock.AnythingOfType("[]txnbuild.Operation"), - nil, - ).Return(horizonResponse, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - assert.Equal(t, 200, statusCode) - assert.NoError(t, json.Unmarshal(response, &parsedResponse)) - assert.Equal(t, horizonResponse, parsedResponse) -} diff --git a/services/bridge/internal/handlers/request_handler_reprocess.go b/services/bridge/internal/handlers/request_handler_reprocess.go deleted file mode 100644 index a9a17c468c..0000000000 --- a/services/bridge/internal/handlers/request_handler_reprocess.go +++ /dev/null @@ -1,53 +0,0 @@ -package handlers - -import ( - "net/http" - - log "github.com/sirupsen/logrus" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" -) - -// Authorize implements /reprocess endpoint -func (rh *RequestHandler) Reprocess(w http.ResponseWriter, r *http.Request) { - request := &bridge.ReprocessRequest{} - err := helpers.FromRequest(r, request) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InvalidParameterError) - return - } - - err = helpers.Validate(request) - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - operation, err := rh.Horizon.OperationDetail(request.OperationID) - if err != nil { - helpers.Write(w, &bridge.ReprocessResponse{Status: "error", Message: err.Error()}) - return - } - - bridgePayment, err := rh.PaymentListener.ConvertToBridgePayment(operation) - if err != nil { - helpers.Write(w, &bridge.ReprocessResponse{Status: "error", Message: err.Error()}) - return - } - - err = rh.PaymentListener.ReprocessPayment(bridgePayment, request.Force) - - if err != nil { - helpers.Write(w, &bridge.ReprocessResponse{Status: "error", Message: err.Error()}) - return - } - - helpers.Write(w, &bridge.ReprocessResponse{Status: "ok"}) -} diff --git a/services/bridge/internal/listener/payment_listener.go b/services/bridge/internal/listener/payment_listener.go deleted file mode 100644 index 307b284825..0000000000 --- a/services/bridge/internal/listener/payment_listener.go +++ /dev/null @@ -1,441 +0,0 @@ -package listener - -import ( - "context" - "crypto/hmac" - "crypto/sha256" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "encoding/base64" - - "github.com/sirupsen/logrus" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/protocols/horizon/effects" - "github.com/stellar/go/protocols/horizon/operations" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/strkey" - "github.com/stellar/go/support/errors" -) - -// PaymentListener is listening for a new payments received by ReceivingAccount -type PaymentListener struct { - client HTTP - config *config.Config - database db.Database - horizon hc.ClientInterface - log *logrus.Entry - now func() time.Time -} - -// HTTP represents an http client that a payment listener can use to make HTTP -// requests. -type HTTP interface { - Do(req *http.Request) (resp *http.Response, err error) -} - -const callbackTimeout = 60 * time.Second - -// NewPaymentListener creates a new PaymentListener -func NewPaymentListener( - config *config.Config, - database db.Database, - horizon hc.ClientInterface, - now func() time.Time, -) (pl PaymentListener, err error) { - pl.client = &http.Client{ - Timeout: callbackTimeout, - } - pl.config = config - pl.database = database - pl.horizon = horizon - pl.now = now - pl.log = logrus.WithFields(logrus.Fields{ - "service": "PaymentListener", - }) - return -} - -// Listen starts listening for new payments -func (pl *PaymentListener) Listen() (err error) { - accountID := pl.config.Accounts.ReceivingAccountID - accountRequest := hc.AccountRequest{AccountID: accountID} - _, err = pl.horizon.AccountDetail(accountRequest) - if err != nil { - return - } - - go func() { - for { - cursorValue, err := pl.database.GetLastCursorValue() - if err != nil { - pl.log.WithFields(logrus.Fields{"error": err}).Error("Could not load last cursor from the DB") - return - } - - var cursor string - if cursorValue != nil { - cursor = *cursorValue - } else { - // If no last cursor saved set it to: `now` - cursor = "now" - } - - pl.log.WithFields(logrus.Fields{ - "accountId": accountID, - "cursor": cursor, - }).Info("Started listening for new payments") - - paymentRequest := hc.OperationRequest{ForAccount: accountID, Cursor: cursor} - err = pl.horizon.StreamPayments(context.Background(), paymentRequest, pl.onPayment) - if err != nil { - pl.log.Error("Error while streaming: ", err) - pl.log.Info("Sleeping...") - time.Sleep(10 * time.Second) - } - } - }() - - return -} - -func (pl *PaymentListener) ReprocessPayment(payment bridge.PaymentResponse, force bool) error { - pl.log.WithFields(logrus.Fields{"id": payment.ID}).Info("Reprocessing a payment") - - existingPayment, err := pl.database.GetReceivedPaymentByOperationID(payment.ID) - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Error checking if receive payment exists") - return err - } - - if existingPayment == nil { - pl.log.WithFields(logrus.Fields{"id": payment.ID}).Info("Payment has not been processed yet") - return errors.New("Payment has not been processed yet") - } - - if existingPayment.Status == "Success" && !force { - pl.log.WithFields(logrus.Fields{"id": payment.ID}).Info("Trying to reprocess successful transaction without force") - return errors.New("Trying to reprocess successful transaction without force") - } - - existingPayment.Status = "Reprocessing..." - existingPayment.ProcessedAt = pl.now() - - err = pl.database.UpdateReceivedPayment(existingPayment) - if err != nil { - return err - } - - err = pl.process(payment) - - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Payment reprocessed with errors") - existingPayment.Status = err.Error() - } else { - pl.log.Info("Payment successfully reprocessed") - existingPayment.Status = "Success" - } - - return pl.database.UpdateReceivedPayment(existingPayment) -} - -func (pl *PaymentListener) onPayment(payment operations.Operation) { - pl.log.WithFields(logrus.Fields{"id": payment.GetID()}).Info("New received payment") - - existingPayment, err := pl.database.GetReceivedPaymentByOperationID(payment.GetID()) - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Error checking if receive payment exists") - return - } - - if existingPayment != nil { - pl.log.WithFields(logrus.Fields{"id": payment.GetID()}).Info("Payment already exists") - return - } - - dbPayment := &db.ReceivedPayment{ - OperationID: payment.GetID(), - TransactionID: payment.GetTransactionHash(), - ProcessedAt: pl.now(), - PagingToken: payment.PagingToken(), - Status: "Processing...", - } - - err = pl.database.InsertReceivedPayment(dbPayment) - if err != nil { - return - } - - bPayment, err := pl.ConvertToBridgePayment(payment) - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Error when converting operation to bridge payment type") - dbPayment.Status = err.Error() - err = pl.database.UpdateReceivedPayment(dbPayment) - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Error updating payment") - return - } - return - } - - process, status := pl.shouldProcessPayment(bPayment) - if !process { - dbPayment.Status = status - pl.log.Info(status) - } else { - err = pl.process(bPayment) - - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Payment processed with errors") - dbPayment.Status = err.Error() - } else { - pl.log.Info("Payment successfully processed") - dbPayment.Status = "Success" - } - } - - err = pl.database.UpdateReceivedPayment(dbPayment) - if err != nil { - pl.log.WithFields(logrus.Fields{"err": err}).Error("Error updating payment") - return - } -} - -// shouldProcessPayment returns false and text status if payment should not be processed -// (ex. asset is different than allowed assets). -func (pl *PaymentListener) shouldProcessPayment(payment bridge.PaymentResponse) (bool, string) { - if payment.Type != "payment" && payment.Type != "path_payment" && payment.Type != "account_merge" { - return false, "Not a payment operation" - } - - if payment.To != pl.config.Accounts.ReceivingAccountID { - return false, "Operation type not permitted" - } - - if !pl.isAssetAllowed(payment.AssetType, payment.AssetCode, payment.AssetIssuer) { - return false, "Asset not allowed" - } - - return true, "" -} - -func (pl *PaymentListener) process(payment bridge.PaymentResponse) error { - pl.log.WithFields(logrus.Fields{"memo": payment.Memo, "type": payment.MemoType}).Info("Loaded memo") - - var receiveResponse callback.ReceiveResponse - var route string - - // Request extra_memo from compliance server - if pl.config.Compliance != "" && payment.MemoType == "hash" { - complianceRequestURL := pl.config.Compliance + "/receive" - complianceRequestBody := url.Values{"memo": {string(payment.Memo)}} - - pl.log.WithFields(logrus.Fields{"url": complianceRequestURL, "body": complianceRequestBody}).Info("Sending request to compliance server") - var resp *http.Response - resp, err := pl.postForm(complianceRequestURL, complianceRequestBody) - if err != nil { - return errors.Wrap(err, "Error sending request to compliance server") - } - - defer resp.Body.Close() - var body []byte - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Wrap(err, "Error reading compliance server response") - } - - if resp.StatusCode != 200 { - pl.log.WithFields(logrus.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from compliance server") - return errors.New("Error response from compliance server") - } - - err = json.Unmarshal([]byte(body), &receiveResponse) - if err != nil { - return errors.Wrap(err, "Cannot unmarshal receiveResponse") - } - - var authData compliance.AuthData - err = json.Unmarshal([]byte(receiveResponse.Data), &authData) - if err != nil { - return errors.Wrap(err, "Cannot unmarshal authData") - } - - var attachment compliance.Attachment - err = json.Unmarshal([]byte(authData.AttachmentJSON), &attachment) - if err != nil { - return errors.Wrap(err, "Cannot unmarshal memo") - } - - route = string(attachment.Transaction.Route) - } else if payment.MemoType != "hash" { - route = payment.Memo - } - - resp, err := pl.postForm( - pl.config.Callbacks.Receive, - url.Values{ - "id": {payment.ID}, - "from": {payment.From}, - "route": {route}, - "amount": {payment.Amount}, - "asset_code": {payment.AssetCode}, - "asset_issuer": {payment.AssetIssuer}, - "memo_type": {payment.MemoType}, - "memo": {payment.Memo}, - "data": {receiveResponse.Data}, - "transaction_id": {payment.TransactionHash}, - }, - ) - if err != nil { - return errors.Wrap(err, "Error sending request to receive callback") - } - - if resp.StatusCode != 200 { - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Wrap(err, "Error reading receive callback response") - } - - pl.log.WithFields(logrus.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from receive callback") - return errors.New("Error response from receive callback") - } - - return nil -} - -func (pl *PaymentListener) isAssetAllowed(assetType string, code string, issuer string) bool { - for _, asset := range pl.config.Assets { - if asset.Code == code && asset.Issuer == issuer { - return true - } - - if asset.Code == "XLM" && asset.Issuer == "" && assetType == "native" { - return true - } - } - return false -} - -func (pl *PaymentListener) postForm( - url string, - form url.Values, -) (*http.Response, error) { - - strbody := form.Encode() - - req, err := http.NewRequest("POST", url, strings.NewReader(strbody)) - if err != nil { - return nil, errors.Wrap(err, "configure http request failed") - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - if pl.config.MACKey != "" { - var rawMAC []byte - rawMAC, err = pl.getMAC(pl.config.MACKey, []byte(strbody)) - if err != nil { - return nil, errors.Wrap(err, "getMAC failed") - } - - encMAC := base64.StdEncoding.EncodeToString(rawMAC) - req.Header.Set("X_PAYLOAD_MAC", encMAC) - req.Header.Set("X-Payload-Mac", encMAC) - } - - resp, err := pl.client.Do(req) - if err != nil { - return nil, errors.Wrap(err, "http request errored") - } - - return resp, nil -} - -func (pl *PaymentListener) getMAC(key string, raw []byte) ([]byte, error) { - - rawkey, err := strkey.Decode(strkey.VersionByteSeed, pl.config.MACKey) - if err != nil { - return nil, errors.Wrap(err, "invalid MAC key") - } - - macer := hmac.New(sha256.New, rawkey) - macer.Write(raw) - return macer.Sum(nil), nil -} - -// ConvertToBridgePayment constructs a bridge.PaymentResponse struct from the operation received from horizon. -// This is done in order to have a standard response because the response from horizon can either be a -// payment, path_payment or account_merge operation; all of which have different fields. -func (pl *PaymentListener) ConvertToBridgePayment(op operations.Operation) (bridge.PaymentResponse, error) { - - payment := bridge.PaymentResponse{ - ID: op.GetID(), - Type: op.GetType(), - PagingToken: op.PagingToken(), - TransactionHash: op.GetTransactionHash(), - } - - switch v := op.(type) { - case operations.Payment: - payment.AssetCode = v.Asset.Code - payment.AssetIssuer = v.Asset.Issuer - payment.AssetType = v.Asset.Type - payment.From = v.From - payment.To = v.To - payment.Amount = v.Amount - case operations.PathPayment: - payment.AssetCode = v.Asset.Code - payment.AssetIssuer = v.Asset.Issuer - payment.AssetType = v.Asset.Type - payment.From = v.From - payment.To = v.To - payment.Amount = v.Amount - case operations.AccountMerge: - payment.AssetCode = "XLM" - payment.AssetIssuer = "" - payment.AssetType = "native" - payment.From = v.Account - payment.To = v.Into - // get amount - effectRequest := hc.EffectRequest{ForOperation: payment.ID} - page, err := pl.horizon.Effects(effectRequest) - if err != nil { - return bridge.PaymentResponse{}, errors.Wrap(err, "unable to get account merge effect") - } - for _, effect := range page.Embedded.Records { - if effect.GetType() == "account_credited" { - ace, ok := effect.(effects.AccountCredited) - if ok { - payment.Amount = ace.Amount - } else { - return bridge.PaymentResponse{}, errors.New("unable to get amount from effect") - } - } - } - - default: - return bridge.PaymentResponse{}, errors.New("operation type not permitted") - } - - transaction, err := pl.horizon.TransactionDetail(payment.TransactionHash) - if err != nil { - return bridge.PaymentResponse{}, errors.Wrap(err, "unable to get transaction details") - } - payment.MemoType = transaction.MemoType - payment.Memo = transaction.Memo - - return payment, nil -} diff --git a/services/bridge/internal/listener/payment_listener_test.go b/services/bridge/internal/listener/payment_listener_test.go deleted file mode 100644 index 1c64274a3d..0000000000 --- a/services/bridge/internal/listener/payment_listener_test.go +++ /dev/null @@ -1,563 +0,0 @@ -package listener - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/protocols/compliance" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/protocols/horizon/base" - "github.com/stellar/go/protocols/horizon/effects" - "github.com/stellar/go/protocols/horizon/operations" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/bridge/internal/mocks" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/strkey" - "github.com/stellar/go/support/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var plconfig = &config.Config{ - Assets: []protocols.Asset{ - {Code: "USD", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - {Code: "EUR", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - }, - Accounts: config.Accounts{ - IssuingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", - ReceivingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", - }, - Callbacks: config.Callbacks{ - Receive: "http://receive_callback", - }, -} - -var mockHorizon = new(hc.MockClient) -var mockDatabase = new(mocks.MockDatabase) -var mockHTTPClient = new(mocks.MockHTTPClient) - -func ensurePaymentStatus(t *testing.T, operation operations.Operation, status string) func(args mock.Arguments) { - return func(args mock.Arguments) { - payment := args.Get(0).(*db.ReceivedPayment) - assert.Equal(t, operation.GetID(), payment.OperationID) - assert.Equal(t, mocks.PredefinedTime, payment.ProcessedAt) - assert.Equal(t, operation.PagingToken(), payment.PagingToken) - assert.Equal(t, status, payment.Status) - assert.Equal(t, operation.GetTransactionHash(), payment.TransactionID) - } -} - -func setDefaultPaymentOperation() operations.Payment { - baseOp := operations.Base{ - ID: "1", - PT: "2", - TransactionSuccessful: true, - Type: "payment", - TransactionHash: "ad71fc31bfae25b0bd14add4cc5306661edf84cdd73f1353d2906363899167e1", - } - - paymentAsset := base.Asset{ - Type: "native", - } - - paymentOp := operations.Payment{ - Asset: paymentAsset, - Base: baseOp, - From: "ABC", - To: "XYZ", - Amount: "100", - } - - return paymentOp -} - -func resetConfig() *config.Config { - return &config.Config{ - Assets: []protocols.Asset{ - {Code: "USD", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - {Code: "EUR", Issuer: "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR"}, - }, - Accounts: config.Accounts{ - IssuingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", - ReceivingAccountID: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", - }, - Callbacks: config.Callbacks{ - Receive: "http://receive_callback", - }, - } - -} - -func TestPaymentListener(t *testing.T) { - - paymentListener, err := NewPaymentListener( - plconfig, - mockDatabase, - mockHorizon, - mocks.Now, - ) - require.NoError(t, err) - paymentListener.client = mockHTTPClient - - paymentOp := setDefaultPaymentOperation() - - mocks.PredefinedTime = time.Now() - - // When operation exists it should save the status - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(&db.ReceivedPayment{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when operation is not a payment it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "create_account" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Not a payment operation")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when operation is not permitted it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GDNXBMIJLLLXZYKZBHXJ45WQ4AJQBRVT776YKGQTDBHTSPMNAFO3OZOS" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Operation type not permitted")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when asset issuer is not allowed it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GC4WWLMUGZJMRVJM7JUVVZBY3LJ5HL4RKIPADEGKEMLAAJEDRONUGYG7" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Asset not allowed")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when asset code is not allowed it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "GBP" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Asset not allowed")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when payment is XLM (no XLM asset in config) it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.From = "GBL27BKG2JSDU6KQ5YJKCDWTVIU24VTG4PLB63SF4K2DBZS5XZMWRPVU" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "" - paymentOp.Asset.Issuer = "" - paymentOp.Asset.Type = "native" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Asset not allowed")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - - // when payment is XLM (XLM asset in config) it should save the status - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "" - paymentOp.Asset.Issuer = "" - paymentOp.Asset.Type = "native" - plconfig.Assets[1].Code = "XLM" - plconfig.Assets[1].Issuer = "" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Success")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{MemoType: "book", Memo: "testing"}, nil).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // when unable to load transaction memo it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "unable to get transaction details: Connection error")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, errors.New("Connection error")).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // when receive callback returns error it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Error response from receive callback")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{MemoType: "text", Memo: "testing"}, nil).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(503, "ok"), - nil, - ).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // when receive callback returns success it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.Base.TransactionHash = "abc123" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Amount = "100" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Success")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{MemoType: "text", Memo: "testing"}, nil).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Run(func(args mock.Arguments) { - req := args.Get(0).(*http.Request) - - assert.Equal(t, paymentOp.From, req.PostFormValue("from")) - assert.Equal(t, paymentOp.Amount, req.PostFormValue("amount")) - assert.Equal(t, paymentOp.Asset.Code, req.PostFormValue("asset_code")) - assert.Equal(t, paymentOp.Asset.Issuer, req.PostFormValue("asset_issuer")) - assert.Equal(t, paymentOp.Base.TransactionHash, req.PostFormValue("transaction_id")) - assert.Equal(t, "text", req.PostFormValue("memo_type")) - assert.Equal(t, "testing", req.PostFormValue("memo")) - }).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // when receive callback returns success(account merge) it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - accountMergeOp := operations.AccountMerge{ - Base: paymentOp.Base, - Into: "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB", - Account: "GBL27BKG2JSDU6KQ5YJKCDWTVIU24VTG4PLB63SF4K2DBZS5XZMWRPVU", - } - accountMergeOp.Base.Type = "account_merge" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, accountMergeOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, accountMergeOp, "Success")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{MemoType: "text", Memo: "testing"}, nil).Once() - - effectsBase := effects.Base{Type: "account_credited"} - acEffect := effects.AccountCredited{Base: effectsBase, Amount: "100"} - embedded := struct { - Records []effects.Effect - }{ - []effects.Effect{acEffect}, - } - effectsResponse := effects.EffectsPage{ - Embedded: embedded, - } - - mockHorizon.On("Effects", mock.AnythingOfType("horizonclient.EffectRequest")).Return(effectsResponse, nil).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Run(func(args mock.Arguments) { - req := args.Get(0).(*http.Request) - - assert.Equal(t, accountMergeOp.Account, req.PostFormValue("from")) - assert.Equal(t, paymentOp.Amount, req.PostFormValue("amount")) - assert.Equal(t, "XLM", req.PostFormValue("asset_code")) - assert.Equal(t, "", req.PostFormValue("asset_issuer")) - assert.Equal(t, "text", req.PostFormValue("memo_type")) - assert.Equal(t, "testing", req.PostFormValue("memo")) - }).Once() - - paymentListener.onPayment(accountMergeOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // when receive callback returns success(no memo) it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Amount = "100" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Success")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{}, nil).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) - - // receive callback returns success and compliance server is connected it should save the status - plconfig = resetConfig() - paymentOp = setDefaultPaymentOperation() - paymentOp.Base.Type = "payment" - paymentOp.To = "GATKP6ZQM5CSLECPMTAC5226PE367QALCPM6AFHTSULPPZMT62OOPMQB" - paymentOp.Amount = "100" - paymentOp.Asset.Code = "USD" - paymentOp.Asset.Issuer = "GD4I7AFSLZGTDL34TQLWJOM2NHLIIOEKD5RHHZUW54HERBLSIRKUOXRR" - - mockDatabase.On("InsertReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Processing...")).Return(nil).Once() - - mockDatabase.On("UpdateReceivedPayment", mock.AnythingOfType("*db.ReceivedPayment")). - Run(ensurePaymentStatus(t, paymentOp, "Success")).Return(nil).Once() - - mockDatabase.On("GetReceivedPaymentByOperationID", "1").Return(nil, nil).Once() - - mockHorizon.On("TransactionDetail", mock.AnythingOfType("string")).Return(hProtocol.Transaction{MemoType: "hash", Memo: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"}, nil).Once() - - attachment := compliance.Attachment{ - Transaction: compliance.Transaction{ - Route: "jed*stellar.org", - }, - } - - attachmentString, _ := json.Marshal(attachment) - - auth := compliance.AuthData{ - AttachmentJSON: string(attachmentString), - } - - authString, _ := json.Marshal(auth) - - response := callback.ReceiveResponse{ - Data: string(authString), - } - - responseString, _ := json.Marshal(response) - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://compliance/receive" - }), - ).Return( - mocks.BuildHTTPResponse(200, string(responseString)), - nil, - ).Once() - - mockHTTPClient.On( - "Do", - mock.MatchedBy(func(req *http.Request) bool { - return req.URL.String() == "http://receive_callback" - }), - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - paymentListener.onPayment(paymentOp) - assert.Nil(t, err) - mockDatabase.AssertExpectations(t) - mockHorizon.AssertExpectations(t) -} - -func TestPostForm_MACKey(t *testing.T) { - validKey := "SABLR5HOI2IUOYB27TR4TO7HWDJIGSRJTT4UUTXXZOFVVPGQKJ5ME43J" - rawkey, err := strkey.Decode(strkey.VersionByteSeed, validKey) - require.NoError(t, err) - - handler := http.NewServeMux() - handler.HandleFunc("/no_mac", func(w http.ResponseWriter, req *http.Request) { - assert.Empty(t, req.Header.Get("X_PAYLOAD_MAC"), "unexpected MAC present") - }) - handler.HandleFunc("/mac", func(w http.ResponseWriter, req *http.Request) { - body, bodyReadErr := ioutil.ReadAll(req.Body) - require.NoError(t, bodyReadErr) - - macer := hmac.New(sha256.New, rawkey) - macer.Write(body) - rawExpected := macer.Sum(nil) - encExpected := base64.StdEncoding.EncodeToString(rawExpected) - - assert.Equal(t, encExpected, req.Header.Get("X_PAYLOAD_MAC"), "MAC is wrong") - }) - - srv := httptest.NewServer(handler) - defer srv.Close() - - cfg := &config.Config{} - pl, err := NewPaymentListener(cfg, nil, nil, nil) - require.NoError(t, err) - - // no mac if the key is not set - _, err = pl.postForm(srv.URL+"/no_mac", url.Values{"foo": []string{"base"}}) - require.NoError(t, err) - - // generates a valid mac if a key is set. - cfg.MACKey = validKey - _, err = pl.postForm(srv.URL+"/mac", url.Values{"foo": []string{"base"}}) - require.NoError(t, err) - - // errors is the key is invalid - cfg.MACKey = "broken" - _, err = pl.postForm(srv.URL+"/mac", url.Values{"foo": []string{"base"}}) - - if assert.Error(t, err) { - assert.Contains(t, err.Error(), "invalid MAC key") - } -} diff --git a/services/bridge/internal/mocks/main.go b/services/bridge/internal/mocks/main.go deleted file mode 100644 index aead21d7fa..0000000000 --- a/services/bridge/internal/mocks/main.go +++ /dev/null @@ -1,104 +0,0 @@ -package mocks - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "time" - - "github.com/stellar/go/support/http/httptest" -) - -// BuildHTTPResponse is used in tests -func BuildHTTPResponse(statusCode int, body string) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Body: ioutil.NopCloser(bytes.NewBufferString(body)), - } -} - -// GetResponse is used in tests -func GetResponse(testServer *httptest.Server, values url.Values) (int, []byte) { - res, err := http.PostForm(testServer.URL, values) - if err != nil { - panic(err) - } - response, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - panic(err) - } - return res.StatusCode, response -} - -// JSONGetResponse is used in tests -func JSONGetResponse(testServer *httptest.Server, data map[string]interface{}) (int, []byte) { - j, err := json.Marshal(data) - if err != nil { - panic(err) - } - req, err := http.NewRequest("POST", testServer.URL, bytes.NewBuffer(j)) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - panic(err) - } - - response, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - panic(err) - } - return res.StatusCode, response -} - -// PredefinedTime is a time.Time object that will be returned by Now() function -var PredefinedTime time.Time - -// Now is a mocking a method -func Now() time.Time { - return PredefinedTime -} - -type Operation interface { - PagingToken() string - GetType() string - GetID() string - GetTransactionHash() string - IsTransactionSuccessful() bool -} - -type MockOperationResponse struct { - PT string - Type string - ID string - TransactionHash string - TransactionSuccessful bool -} - -func (m MockOperationResponse) PagingToken() string { - return m.PT -} - -func (m MockOperationResponse) GetType() string { - return m.Type -} - -func (m MockOperationResponse) GetID() string { - return m.ID -} - -func (m MockOperationResponse) GetTransactionHash() string { - return m.TransactionHash -} - -func (m MockOperationResponse) IsTransactionSuccessful() bool { - return m.TransactionSuccessful -} diff --git a/services/bridge/internal/mocks/mock_database.go b/services/bridge/internal/mocks/mock_database.go deleted file mode 100644 index 6e6bc27731..0000000000 --- a/services/bridge/internal/mocks/mock_database.go +++ /dev/null @@ -1,83 +0,0 @@ -package mocks - -import ( - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stretchr/testify/mock" -) - -// MockDatabase ... -type MockDatabase struct { - mock.Mock -} - -// GetLastCursorValue is a mocking a method -func (m *MockDatabase) GetLastCursorValue() (cursor *string, err error) { - a := m.Called() - return a.Get(0).(*string), a.Error(1) -} - -// InsertReceivedPayment is a mocking a method -func (m *MockDatabase) InsertReceivedPayment(payment *db.ReceivedPayment) error { - a := m.Called(payment) - return a.Error(0) -} - -// UpdateReceivedPayment is a mocking a method -func (m *MockDatabase) UpdateReceivedPayment(payment *db.ReceivedPayment) error { - a := m.Called(payment) - return a.Error(0) -} - -// GetReceivedPaymentByID is a mocking a method -func (m *MockDatabase) GetReceivedPaymentByID(operationID int64) (*db.ReceivedPayment, error) { - a := m.Called(operationID) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.ReceivedPayment), a.Error(1) -} - -// GetReceivedPaymentByOperationID is a mocking a method -func (m *MockDatabase) GetReceivedPaymentByOperationID(operationID string) (*db.ReceivedPayment, error) { - a := m.Called(operationID) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.ReceivedPayment), a.Error(1) -} - -func (m *MockDatabase) GetReceivedPayments(page, limit uint64) ([]*db.ReceivedPayment, error) { - a := m.Called(page, limit) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).([]*db.ReceivedPayment), a.Error(1) -} - -// InsertSentTransaction is a mocking a method -func (m *MockDatabase) InsertSentTransaction(transaction *db.SentTransaction) error { - a := m.Called(transaction) - return a.Error(0) -} - -// UpdateSentTransaction is a mocking a method -func (m *MockDatabase) UpdateSentTransaction(transaction *db.SentTransaction) error { - a := m.Called(transaction) - return a.Error(0) -} - -func (m *MockDatabase) GetSentTransactions(page, limit uint64) ([]*db.SentTransaction, error) { - a := m.Called(page, limit) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).([]*db.SentTransaction), a.Error(1) -} - -func (m *MockDatabase) GetSentTransactionByPaymentID(paymentID string) (*db.SentTransaction, error) { - a := m.Called(paymentID) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.SentTransaction), a.Error(1) -} diff --git a/services/bridge/internal/mocks/mock_federation_resolve.go b/services/bridge/internal/mocks/mock_federation_resolve.go deleted file mode 100644 index 62017c5832..0000000000 --- a/services/bridge/internal/mocks/mock_federation_resolve.go +++ /dev/null @@ -1,31 +0,0 @@ -package mocks - -import ( - "net/url" - - fprotocol "github.com/stellar/go/protocols/federation" - "github.com/stretchr/testify/mock" -) - -// MockFederationResolver ... -type MockFederationResolver struct { - mock.Mock -} - -// LookupByAddress is a mocking a method -func (m *MockFederationResolver) LookupByAddress(addy string) (*fprotocol.NameResponse, error) { - a := m.Called(addy) - return a.Get(0).(*fprotocol.NameResponse), a.Error(1) -} - -// LookupByAccountID is a mocking a method -func (m *MockFederationResolver) LookupByAccountID(aid string) (*fprotocol.IDResponse, error) { - a := m.Called(aid) - return a.Get(0).(*fprotocol.IDResponse), a.Error(1) -} - -// ForwardRequest is a mocking a method -func (m *MockFederationResolver) ForwardRequest(domain string, fields url.Values) (*fprotocol.NameResponse, error) { - a := m.Called(domain, fields) - return a.Get(0).(*fprotocol.NameResponse), a.Error(1) -} diff --git a/services/bridge/internal/mocks/mock_http_client.go b/services/bridge/internal/mocks/mock_http_client.go deleted file mode 100644 index ac3a7d9c45..0000000000 --- a/services/bridge/internal/mocks/mock_http_client.go +++ /dev/null @@ -1,37 +0,0 @@ -package mocks - -import ( - "net/http" - "net/url" - - "github.com/stretchr/testify/mock" -) - -// MockHTTPClient ... -type MockHTTPClient struct { - mock.Mock -} - -// HTTPClientInterface helps mocking http.Client in tests -type HTTPClientInterface interface { - PostForm(url string, data url.Values) (resp *http.Response, err error) - Get(url string) (resp *http.Response, err error) -} - -// PostForm is a mocking a method -func (m *MockHTTPClient) PostForm(url string, data url.Values) (resp *http.Response, err error) { - a := m.Called(url, data) - return a.Get(0).(*http.Response), a.Error(1) -} - -// Get is a mocking a method -func (m *MockHTTPClient) Get(url string) (resp *http.Response, err error) { - a := m.Called(url) - return a.Get(0).(*http.Response), a.Error(1) -} - -// Do is a mocking a method -func (m *MockHTTPClient) Do(req *http.Request) (resp *http.Response, err error) { - a := m.Called(req) - return a.Get(0).(*http.Response), a.Error(1) -} diff --git a/services/bridge/internal/mocks/mock_stellar_toml_resolver.go b/services/bridge/internal/mocks/mock_stellar_toml_resolver.go deleted file mode 100644 index 5a4ae2338b..0000000000 --- a/services/bridge/internal/mocks/mock_stellar_toml_resolver.go +++ /dev/null @@ -1,23 +0,0 @@ -package mocks - -import ( - "github.com/stellar/go/clients/stellartoml" - "github.com/stretchr/testify/mock" -) - -// MockStellartomlResolver ... -type MockStellartomlResolver struct { - mock.Mock -} - -// GetStellarToml is a mocking a method -func (m *MockStellartomlResolver) GetStellarToml(domain string) (resp *stellartoml.Response, err error) { - a := m.Called(domain) - return a.Get(0).(*stellartoml.Response), a.Error(1) -} - -// GetStellarTomlByAddress is a mocking a method -func (m *MockStellartomlResolver) GetStellarTomlByAddress(addy string) (*stellartoml.Response, error) { - a := m.Called(addy) - return a.Get(0).(*stellartoml.Response), a.Error(1) -} diff --git a/services/bridge/internal/mocks/mock_transaction_submitter.go b/services/bridge/internal/mocks/mock_transaction_submitter.go deleted file mode 100644 index a88704441b..0000000000 --- a/services/bridge/internal/mocks/mock_transaction_submitter.go +++ /dev/null @@ -1,25 +0,0 @@ -package mocks - -import ( - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/mock" -) - -// MockTransactionSubmitter mocks TransactionSubmitter -type MockTransactionSubmitter struct { - mock.Mock -} - -// SubmitTransaction is a mocking a method -func (ts *MockTransactionSubmitter) SubmitTransaction(paymentID *string, seed string, operation []txnbuild.Operation, memo txnbuild.Memo) (hProtocol.Transaction, error) { - a := ts.Called(paymentID, seed, operation, memo) - return a.Get(0).(hProtocol.Transaction), a.Error(1) -} - -// SignAndSubmitRawTransaction is a mocking a method -func (ts *MockTransactionSubmitter) SignAndSubmitRawTransaction(paymentID *string, seed string, tx *xdr.Transaction) (hProtocol.Transaction, error) { - a := ts.Called(paymentID, seed, tx) - return a.Get(0).(hProtocol.Transaction), a.Error(1) -} diff --git a/services/bridge/internal/submitter/transaction_submitter.go b/services/bridge/internal/submitter/transaction_submitter.go deleted file mode 100644 index ebd877ff16..0000000000 --- a/services/bridge/internal/submitter/transaction_submitter.go +++ /dev/null @@ -1,308 +0,0 @@ -package submitter - -import ( - "database/sql" - "encoding/hex" - "strconv" - "sync" - "time" - - "github.com/sirupsen/logrus" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/network" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" -) - -// TransactionSubmitterInterface helps mocking TransactionSubmitter -type TransactionSubmitterInterface interface { - SubmitTransaction(paymentID *string, seed string, operation []txnbuild.Operation, memo txnbuild.Memo) (response hProtocol.Transaction, err error) - SignAndSubmitRawTransaction(paymentID *string, seed string, tx *xdr.Transaction) (response hProtocol.Transaction, err error) -} - -// TransactionSubmitter submits transactions to Stellar Network -type TransactionSubmitter struct { - Horizon hc.ClientInterface - Accounts map[string]*Account // seed => *Account - AccountsMutex sync.Mutex - Database db.Database - Network string - log *logrus.Entry - now func() time.Time -} - -// Account represents account used to signing and sending transactions -type Account struct { - Keypair keypair.KP - Seed string - SequenceNumber uint64 - Mutex sync.Mutex -} - -// NewTransactionSubmitter creates a new TransactionSubmitter -func NewTransactionSubmitter( - horizon hc.ClientInterface, - database db.Database, - networkPassphrase string, - now func() time.Time, -) (ts TransactionSubmitter) { - ts.Horizon = horizon - ts.Database = database - ts.Accounts = make(map[string]*Account) - ts.Network = networkPassphrase - ts.log = logrus.WithFields(logrus.Fields{ - "service": "TransactionSubmitter", - }) - ts.now = now - return -} - -// LoadAccount loads current state of Stellar account and creates a map entry if it didn't exist -func (ts *TransactionSubmitter) LoadAccount(seed string) (*Account, error) { - ts.AccountsMutex.Lock() - - account, exist := ts.Accounts[seed] - if exist { - ts.AccountsMutex.Unlock() - return account, nil - } - - kp, err := keypair.Parse(seed) - if err != nil { - ts.log.Print("Invalid seed") - ts.AccountsMutex.Unlock() - return nil, err - } - - ts.Accounts[seed] = &Account{ - Seed: seed, - Keypair: kp, - } - ts.AccountsMutex.Unlock() - - // Load account sequence number - ts.Accounts[seed].Mutex.Lock() - defer ts.Accounts[seed].Mutex.Unlock() - - if ts.Accounts[seed].SequenceNumber != 0 { - return ts.Accounts[seed], nil - } - - accountRequest := hc.AccountRequest{AccountID: ts.Accounts[seed].Keypair.Address()} - accountResponse, err := ts.Horizon.AccountDetail(accountRequest) - if err != nil { - return nil, err - } - - ts.Accounts[seed].SequenceNumber, err = strconv.ParseUint(accountResponse.Sequence, 10, 64) - if err != nil { - return nil, err - } - - return ts.Accounts[seed], nil -} - -// InitAccount loads an account and returns error if it fails -func (ts *TransactionSubmitter) InitAccount(seed string) (err error) { - _, err = ts.LoadAccount(seed) - return -} - -// SignAndSubmitRawTransaction will: -// - update sequence number of the transaction to the current one, -// - sign it, -// - submit it to the network. -func (ts *TransactionSubmitter) SignAndSubmitRawTransaction(paymentID *string, seed string, tx *xdr.Transaction) (response hProtocol.Transaction, err error) { - account, err := ts.LoadAccount(seed) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error loading account") - return - } - - account.Mutex.Lock() - account.SequenceNumber++ - tx.SeqNum = xdr.SequenceNumber(account.SequenceNumber) - account.Mutex.Unlock() - - hash, err := network.HashTransaction(*tx, ts.Network) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error calculating transaction hash") - return - } - - sig, err := account.Keypair.SignDecorated(hash[:]) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error signing a transaction") - return - } - - envelopeXdr := xdr.TransactionEnvelope{ - Type: xdr.EnvelopeTypeEnvelopeTypeTx, - V1: &xdr.TransactionV1Envelope{ - Tx: *tx, - Signatures: []xdr.DecoratedSignature{sig}, - }, - } - - txeB64, err := xdr.MarshalBase64(envelopeXdr) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Cannot encode transaction envelope") - return - } - - transactionHashBytes, err := network.HashTransaction(*tx, ts.Network) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Warn("Error calculating tx hash") - return - } - - var herr *hc.Error - response, err = ts.SubmitAndSave(paymentID, account.Keypair.Address(), txeB64, hex.EncodeToString(transactionHashBytes[:])) - if err != nil { - var isHorizonError bool - herr, isHorizonError = err.(*hc.Error) - if !isHorizonError { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error submitting transaction ", err) - return - } - } - - // Sync sequence number - if herr != nil { - codes, rerr := herr.ResultCodes() - if rerr != nil { - return response, herr - } - - if codes.TransactionCode != "tx_bad_seq" { - return response, herr - } - - account.Mutex.Lock() - ts.log.Print("Syncing sequence number for ", account.Keypair.Address()) - - accountRequest := hc.AccountRequest{AccountID: account.Keypair.Address()} - accountResponse, err := ts.Horizon.AccountDetail(accountRequest) - if err != nil { - ts.log.Error("Error updating sequence number ", err) - } else { - account.SequenceNumber, _ = strconv.ParseUint(accountResponse.Sequence, 10, 64) - } - account.Mutex.Unlock() - - return response, herr - } - return -} - -// SubmitTransaction builds and submits transaction to Stellar network -func (ts *TransactionSubmitter) SubmitTransaction(paymentID *string, seed string, operation []txnbuild.Operation, memo txnbuild.Memo) (hProtocol.Transaction, error) { - account, err := ts.LoadAccount(seed) - if err != nil { - return hProtocol.Transaction{}, errors.Wrap(err, "Error loading an account") - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{AccountID: account.Keypair.Address(), Sequence: int64(account.SequenceNumber)}, - IncrementSequenceNum: true, - Operations: operation, - BaseFee: txnbuild.MinBaseFee, - Memo: memo, - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - if err != nil { - ts.log.Error("Unable to build transaction") - return hProtocol.Transaction{}, errors.Wrap(err, "unable to build transaction") - } - - kp, err := keypair.Parse(seed) - if err != nil { - ts.log.Error("Unable to convert seed to keypair") - return hProtocol.Transaction{}, errors.Wrap(err, "unable to convert seed to keypair") - } - - tx, err = tx.Sign(ts.Network, kp.(*keypair.Full)) - if err != nil { - ts.log.Error("Unable to sign transaction") - return hProtocol.Transaction{}, errors.Wrap(err, "unable to sign transaction") - } - - txe, err := tx.Base64() - if err != nil { - ts.log.Error("Unable to encode transaction") - return hProtocol.Transaction{}, errors.Wrap(err, "unable to encode transaction") - } - - txHashBytes, err := tx.Hash(ts.Network) - if err != nil { - ts.log.Error("Unable to get transaction hash") - return hProtocol.Transaction{}, errors.Wrap(err, "unable to get transaction hash") - } - - return ts.SubmitAndSave(paymentID, tx.SourceAccount().AccountID, txe, hex.EncodeToString(txHashBytes[:])) -} - -// SubmitAndSave sumbits a transaction to horizon and saves the details in the bridge server database. -func (ts *TransactionSubmitter) SubmitAndSave(paymentID *string, sourceAccount, txeB64, txHash string) (response hProtocol.Transaction, err error) { - nullPaymentID := sql.NullString{Valid: false} - if paymentID != nil { - nullPaymentID = sql.NullString{ - String: *paymentID, - Valid: true, - } - } - - sentTransaction := &db.SentTransaction{ - PaymentID: nullPaymentID, - TransactionID: txHash, - Status: db.SentTransactionStatusSending, - Source: sourceAccount, - SubmittedAt: ts.now(), - EnvelopeXdr: txeB64, - } - - err = ts.Database.InsertSentTransaction(sentTransaction) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error inserting sent transaction") - return - } - - ts.log.WithFields(logrus.Fields{"tx": txeB64}).Info("Submitting transaction") - - var herr *hc.Error - response, err = ts.Horizon.SubmitTransactionXDR(txeB64) - if err == nil { - sentTransaction.Status = db.SentTransactionStatusSuccess - sentTransaction.Ledger = &response.Ledger - now := time.Now() - sentTransaction.SucceededAt = &now - } else { - var isHorizonError bool - herr, isHorizonError = err.(*hc.Error) - if !isHorizonError { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error submitting transaction ", err) - } else { - var result string - result, err = herr.ResultString() - if err != nil { - result = errors.Wrap(err, "Error getting tx result").Error() - } - sentTransaction.ResultXdr = &result - } - sentTransaction.Status = db.SentTransactionStatusFailure - } - - err = ts.Database.UpdateSentTransaction(sentTransaction) - if err != nil { - ts.log.WithFields(logrus.Fields{"err": err}).Error("Error updating sent transaction") - return - } - - return -} diff --git a/services/bridge/internal/submitter/transaction_submitter_test.go b/services/bridge/internal/submitter/transaction_submitter_test.go deleted file mode 100644 index dd7eb8342e..0000000000 --- a/services/bridge/internal/submitter/transaction_submitter_test.go +++ /dev/null @@ -1,194 +0,0 @@ -package submitter - -import ( - "fmt" - "testing" - "time" - - hc "github.com/stellar/go/clients/horizonclient" - hProtocol "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/bridge/internal/mocks" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/txnbuild" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestTransactionSubmitter(t *testing.T) { - var mockHorizon = new(hc.MockClient) - var mockDatabase = new(mocks.MockDatabase) - mocks.PredefinedTime = time.Now() - seed := "SDZT3EJZ7FZRYNTLOZ7VH6G5UYBFO2IO3Q5PGONMILPCZU3AL7QNZHTE" - accountID := "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H" - transactionSubmitter := NewTransactionSubmitter(mockHorizon, mockDatabase, "Test SDF Network ; September 2015", mocks.Now) - - // When seed is invalid - _, err := transactionSubmitter.LoadAccount("invalidSeed") - assert.NotNil(t, err) - - // When there is an error loading account - mockHorizon.On( - "AccountDetail", - mock.AnythingOfType("horizonclient.AccountRequest"), - ).Return( - hProtocol.Account{}, - errors.New("Account not found"), - ).Once() - - _, err = transactionSubmitter.LoadAccount(seed) - assert.NotNil(t, err) - mockHorizon.AssertExpectations(t) - - // successfully loads an account - transactionSubmitter = NewTransactionSubmitter(mockHorizon, mockDatabase, "Test SDF Network ; September 2015", mocks.Now) - - mockHorizon.On( - "AccountDetail", - mock.AnythingOfType("horizonclient.AccountRequest"), - ).Return( - hProtocol.Account{ - ID: accountID, - AccountID: accountID, - Sequence: "10372672437354496", - }, - nil, - ).Once() - - account, err := transactionSubmitter.LoadAccount(seed) - assert.Nil(t, err) - assert.Equal(t, account.Keypair.Address(), accountID) - assert.Equal(t, account.Seed, seed) - assert.Equal(t, account.SequenceNumber, uint64(10372672437354496)) - mockHorizon.AssertExpectations(t) - - // Submit transaction - Error response from horizon - transactionSubmitter = NewTransactionSubmitter(mockHorizon, mockDatabase, "Test SDF Network ; September 2015", mocks.Now) - - mockHorizon.On( - "AccountDetail", - mock.AnythingOfType("horizonclient.AccountRequest"), - ).Return( - hProtocol.Account{ - ID: accountID, - AccountID: accountID, - Sequence: "10372672437354496", - }, - nil, - ).Once() - - err = transactionSubmitter.InitAccount(seed) - assert.Nil(t, err) - - txB64 := "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAHdv1hoGkOgiXF0LRkRaHa7m0GfXIuWT0ZcaajT1ldQgAAAAAAAAAAA7msoAAAAAAAAAAAHMPq4QAAAAQMk5tSJngfsKsfYxK5VqfFCSwgqGatSnp54Lm+WVrMD5wNVFMaHHflIJrzUDS0+/uTeh6lzpIRHRYRUOTAfKpAc=" - - // Persist sending transaction - mockDatabase.On( - "InsertSentTransaction", - mock.AnythingOfType("*db.SentTransaction"), - ).Return(nil).Once().Run(func(args mock.Arguments) { - transaction := args.Get(0).(*db.SentTransaction) - assert.Equal(t, "b2ce8447092ccfbb486aa9d38251a19df7df8df16c0c59edc55fee4d9727626e", transaction.TransactionID) - assert.Equal(t, "sending", string(transaction.Status)) - assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) - assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) - assert.Equal(t, txB64, transaction.EnvelopeXdr) - }) - - // Persist failure - mockDatabase.On( - "UpdateSentTransaction", - mock.AnythingOfType("*db.SentTransaction"), - ).Return(nil).Once().Run(func(args mock.Arguments) { - transaction := args.Get(0).(*db.SentTransaction) - assert.Equal(t, "b2ce8447092ccfbb486aa9d38251a19df7df8df16c0c59edc55fee4d9727626e", transaction.TransactionID) - fmt.Println("txStatus: ", transaction.Status) - assert.Equal(t, "failure", string(transaction.Status)) - assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) - assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) - assert.Equal(t, txB64, transaction.EnvelopeXdr) - }) - - mockHorizon.On("SubmitTransactionXDR", txB64).Return( - hProtocol.Transaction{ - Ledger: 0, - ResultXdr: "AAAAAAAAAGT/////AAAAAQAAAAAAAAAB////+wAAAAA=", // no_destination - - }, - errors.New("tx failed"), - ).Once() - - txnOp := &txnbuild.Payment{ - Destination: "GB3W7VQ2A2IOQIS4LUFUMRC2DWXONUDH24ROLE6RS4NGUNHVSXKCABOM", - Amount: "100", - Asset: txnbuild.NativeAsset{}, - } - - _, err = transactionSubmitter.SubmitTransaction((*string)(nil), seed, []txnbuild.Operation{txnOp}, nil) - assert.Nil(t, err) - mockHorizon.AssertExpectations(t) - - // Submit transaction - success response - transactionSubmitter = NewTransactionSubmitter(mockHorizon, mockDatabase, "Test SDF Network ; September 2015", mocks.Now) - - mockHorizon.On( - "AccountDetail", - mock.AnythingOfType("horizonclient.AccountRequest"), - ).Return( - hProtocol.Account{ - ID: accountID, - AccountID: accountID, - Sequence: "10372672437354496", - }, - nil, - ).Once() - - err = transactionSubmitter.InitAccount(seed) - assert.Nil(t, err) - - txB64 = "AAAAAJbmB/pwwloZXCaCr9WR3Fue2lNhHGaDWKVOWO7MPq4QAAAAZAAk2eQAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAHdv1hoGkOgiXF0LRkRaHa7m0GfXIuWT0ZcaajT1ldQgAAAAADuaygAAAAAAAAAAAcw+rhAAAABAPgwRbiJH9d4zukMq8ULwe88YCLniYFbq9YgryxS+VmYIJ7N6KKbsRMWi2LDMovRY2I6f3GG8eBHUh0JCwTbdCg==" - - // Persist sending transaction - mockDatabase.On( - "InsertSentTransaction", - mock.AnythingOfType("*db.SentTransaction"), - ).Return(nil).Once().Run(func(args mock.Arguments) { - transaction := args.Get(0).(*db.SentTransaction) - assert.Equal(t, "90c541957386b0325e66cc1308cfdfcffc5d95fe30210ade896f50a168839a13", transaction.TransactionID) - assert.Equal(t, "sending", string(transaction.Status)) - assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) - assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) - assert.Equal(t, txB64, transaction.EnvelopeXdr) - }) - - // Persist success - mockDatabase.On( - "UpdateSentTransaction", - mock.AnythingOfType("*db.SentTransaction"), - ).Return(nil).Once().Run(func(args mock.Arguments) { - transaction := args.Get(0).(*db.SentTransaction) - assert.Equal(t, "90c541957386b0325e66cc1308cfdfcffc5d95fe30210ade896f50a168839a13", transaction.TransactionID) - assert.Equal(t, "success", string(transaction.Status)) - assert.Equal(t, "GCLOMB72ODBFUGK4E2BK7VMR3RNZ5WSTMEOGNA2YUVHFR3WMH2XBAB6H", transaction.Source) - assert.Equal(t, mocks.PredefinedTime, transaction.SubmittedAt) - assert.Equal(t, txB64, transaction.EnvelopeXdr) - }) - - mockHorizon.On("SubmitTransactionXDR", txB64).Return( - hProtocol.Transaction{ - Ledger: int32(123), - ResultXdr: "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=", - }, - nil, - ).Once() - - txnOp2 := &txnbuild.CreateAccount{ - Destination: "GB3W7VQ2A2IOQIS4LUFUMRC2DWXONUDH24ROLE6RS4NGUNHVSXKCABOM", - Amount: "100", - } - - _, err = transactionSubmitter.SubmitTransaction((*string)(nil), seed, []txnbuild.Operation{txnOp2}, nil) - assert.Nil(t, err) - mockHorizon.AssertExpectations(t) - -} diff --git a/services/bridge/internal/test/helpers.go b/services/bridge/internal/test/helpers.go deleted file mode 100644 index 37a912f3ce..0000000000 --- a/services/bridge/internal/test/helpers.go +++ /dev/null @@ -1,17 +0,0 @@ -package test - -import ( - "encoding/json" -) - -// StringToJSONMap transforms -func StringToJSONMap(value string, ignoredFields ...string) (m map[string]interface{}) { - err := json.Unmarshal([]byte(value), &m) - if err != nil { - panic(err) - } - for _, ignoredField := range ignoredFields { - delete(m, ignoredField) - } - return -} diff --git a/services/bridge/main.go b/services/bridge/main.go deleted file mode 100644 index e36f262d33..0000000000 --- a/services/bridge/main.go +++ /dev/null @@ -1,286 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "os" - "runtime" - "time" - - log "github.com/sirupsen/logrus" - - "github.com/facebookgo/inject" - "github.com/spf13/cobra" - "github.com/stellar/go/clients/federation" - hc "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/services/bridge/internal/config" - "github.com/stellar/go/services/bridge/internal/db" - "github.com/stellar/go/services/bridge/internal/handlers" - "github.com/stellar/go/services/bridge/internal/listener" - "github.com/stellar/go/services/bridge/internal/submitter" - supportConfig "github.com/stellar/go/support/config" - "github.com/stellar/go/support/db/schema" - "github.com/stellar/go/support/errors" - supportHttp "github.com/stellar/go/support/http" - supportLog "github.com/stellar/go/support/log" -) - -var app *App -var rootCmd *cobra.Command -var migrateFlag bool -var configFile string -var versionFlag bool -var version = "N/A" - -func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - rootCmd.Execute() -} - -func init() { - rootCmd = &cobra.Command{ - Use: "bridge", - Short: "stellar bridge server", - Long: `stellar bridge server`, - Run: run, - } - - rootCmd.Flags().BoolVarP(&migrateFlag, "migrate-db", "", false, "migrate DB to the newest schema version") - rootCmd.Flags().StringVarP(&configFile, "config", "c", "bridge.cfg", "path to config file") - rootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "displays bridge server version") -} - -func run(cmd *cobra.Command, args []string) { - var cfg config.Config - - err := supportConfig.Read(configFile, &cfg) - if err != nil { - switch cause := errors.Cause(err).(type) { - case *supportConfig.InvalidConfigError: - log.Error("config file: ", cause) - default: - log.Error(err) - } - os.Exit(-1) - } - - err = cfg.Validate() - if err != nil { - log.Fatal(err.Error()) - return - } - - if cfg.LogFormat == "json" { - log.SetFormatter(&log.JSONFormatter{}) - } - - app, err = NewApp(cfg, migrateFlag, versionFlag, version) - - if err != nil { - log.Fatal(err.Error()) - return - } - - app.Serve() -} - -// App is the application object -type App struct { - config config.Config - requestHandler handlers.RequestHandler -} - -// NewApp constructs an new App instance from the provided config. -func NewApp(config config.Config, migrateFlag bool, versionFlag bool, version string) (app *App, err error) { - var g inject.Graph - - var database db.PostgresDatabase - - if config.Database != nil { - err = database.Open(config.Database.URL) - if err != nil { - err = fmt.Errorf("Cannot connect to a DB: %s", err) - return - } - } - - if migrateFlag { - var migrationsApplied int - migrationsApplied, err = schema.Migrate( - database.GetDB(), - db.Migrations, - schema.MigrateUp, - 0, - ) - if err != nil { - return - } - - log.Info("Applied migrations: ", migrationsApplied) - os.Exit(0) - return - } - - if versionFlag { - fmt.Printf("Bridge Server Version: %s \n", version) - os.Exit(0) - return - } - - if len(config.APIKey) > 0 && len(config.APIKey) < 15 { - err = errors.New("api-key have to be at least 15 chars long") - return - } - - requestHandler := handlers.RequestHandler{} - - httpClientWithTimeout := http.Client{ - Timeout: 60 * time.Second, - } - - h := hc.Client{ - HorizonURL: config.Horizon, - HTTP: http.DefaultClient, - AppName: "bridge-server", - } - - log.Print("Creating and initializing TransactionSubmitter") - ts := submitter.NewTransactionSubmitter(&h, &database, config.NetworkPassphrase, time.Now) - if err != nil { - return - } - - if config.Accounts.AuthorizingSeed == "" { - log.Warning("No accounts.authorizing_seed param. Skipping...") - } else { - log.Print("Initializing Authorizing account") - err = ts.InitAccount(config.Accounts.AuthorizingSeed) - if err != nil { - return - } - } - - if config.Accounts.BaseSeed == "" { - log.Warning("No accounts.base_seed param. Skipping...") - } else { - log.Print("Initializing Base account") - err = ts.InitAccount(config.Accounts.BaseSeed) - if err != nil { - return - } - } - - log.Print("TransactionSubmitter created") - - log.Print("Creating and starting PaymentListener") - - var paymentListener listener.PaymentListener - - if config.Accounts.ReceivingAccountID == "" { - log.Warning("No accounts.receiving_account_id param. Skipping...") - } else if config.Callbacks.Receive == "" { - log.Warning("No callbacks.receive param. Skipping...") - } else { - paymentListener, err = listener.NewPaymentListener(&config, &database, &h, time.Now) - if err != nil { - return - } - err = paymentListener.Listen() - if err != nil { - return - } - - log.Print("PaymentListener created") - } - - stellartomlClient := stellartoml.Client{ - HTTP: &httpClientWithTimeout, - } - - federationClient := federation.Client{ - HTTP: &httpClientWithTimeout, - StellarTOML: &stellartomlClient, - } - - err = g.Provide( - &inject.Object{Value: &requestHandler}, - &inject.Object{Value: &config}, - &inject.Object{Value: &stellartomlClient}, - &inject.Object{Value: &federationClient}, - &inject.Object{Value: &h}, - &inject.Object{Value: &database}, - &inject.Object{Value: &ts}, - &inject.Object{Value: &paymentListener}, - &inject.Object{Value: &httpClientWithTimeout}, - ) - - if err != nil { - log.Fatal("Injector: ", err) - } - - if err := g.Populate(); err != nil { - log.Fatal("Injector: ", err) - } - - app = &App{ - config: config, - requestHandler: requestHandler, - } - return -} - -// Serve starts the server -func (a *App) Serve() { - mux := supportHttp.NewAPIMux(supportLog.DefaultLogger) - - // Middlewares - headers := make(http.Header) - headers.Set("Content-Type", "application/json") - mux.Use(supportHttp.StripTrailingSlashMiddleware("/admin")) - mux.Use(supportHttp.HeadersMiddleware(headers, "/admin/")) - - if a.config.APIKey != "" { - mux.Use(apiKeyMiddleware(a.config.APIKey)) - } - - if a.config.Accounts.AuthorizingSeed != "" { - mux.Post("/authorize", a.requestHandler.Authorize) - } else { - log.Warning("accounts.authorizing_seed not provided. /authorize endpoint will not be available.") - } - - mux.Post("/create-keypair", a.requestHandler.CreateKeypair) - mux.Post("/builder", a.requestHandler.Builder) - mux.Post("/payment", a.requestHandler.Payment) - mux.Get("/payment", a.requestHandler.Payment) - mux.Post("/reprocess", a.requestHandler.Reprocess) - - mux.Get("/admin/received-payments", a.requestHandler.AdminReceivedPayments) - mux.Get("/admin/received-payments/{id}", a.requestHandler.AdminReceivedPayment) - mux.Get("/admin/sent-transactions", a.requestHandler.AdminSentTransactions) - - supportHttp.Run(supportHttp.Config{ - ListenAddr: fmt.Sprintf(":%d", *a.config.Port), - Handler: mux, - OnStarting: func() { - log.Infof("starting bridge server") - log.Infof("listening on %d", *a.config.Port) - }, - }) -} - -// apiKeyMiddleware checks for apiKey in a request and writes http.StatusForbidden if it's incorrect. -func apiKeyMiddleware(apiKey string) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - k := r.PostFormValue("apiKey") - if k != apiKey { - http.Error(w, "Forbidden", http.StatusForbidden) - return - } - next.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) - } -} diff --git a/services/compliance/CHANGELOG.md b/services/compliance/CHANGELOG.md deleted file mode 100644 index 8d3f7995f7..0000000000 --- a/services/compliance/CHANGELOG.md +++ /dev/null @@ -1,198 +0,0 @@ -# Changelog - -As this project is pre 1.0, breaking changes may happen for minor version bumps. A breaking change will get clearly notified in this log. - -## Unreleased - -* Dropped support for Go 1.12. - -## 0.0.33 - -* Add `ReadTimeout` to HTTP server configuration to fix potential DoS vector. -* Dropped support for Go 1.10, 1.11. - -## 0.0.32 - -* Compliance server now uses the new Go SDK. -* Unit tests added. - -## Breaking changes - -* MySQL is no longer supported. To migrate your data to postgresql use any of the tools provided [here](https://wiki.postgresql.org/wiki/Converting_from_other_Databases_to_PostgreSQL#MySQL). - - -## 0.0.31 - -### Breaking changes -* `id` parameter is now required when sending payments using Compliance Protocol. - -### Changes -* `nonce` value does not change when repeating Auth Request after receiving `pending` status. -* Support for sending XLM using Compliance Protocol. -* Fix for #109 - -Please migrate your `compliance` DB before running a new version using: `compliance --migrate-db`. - -## 0.0.30 - -* Support for "Forward federation" destinations. - -## 0.0.29 - -* Improved transaction submission code: - * High rate transaction submission using `/payment` endpoint should work better. - * Added `id` parameter to `/payment` request: payments with `id` set, when resubmitted, are using previously created transaction envelope stored in a DB instead of recreating a transaction with a new sequence number. This can prevent accidental double-spends. -* Fix for a bug in `/builder` endpoint: sequence number is now incremented when loaded from Horizon server (https://github.com/stellar/bridge-server/issues/86). -* Payment listener is now also sending `account_merge` operations and, for each operation, a new parameter: `transaction_id`. -* Updated `github.com/BurntSushi/toml` dependency. - -Read `README` file for more information about new features. - -Please migrate your `bridge` DB before running a new version using: `bridge --migrate-db` - -## 0.0.28 - -* Added error messages to Compliance protocol ([SEP-0003](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0003.md)) - -## 0.0.27 - -* Admin Panel (`/admin` endpoint in `bridge` server). -* `/tx_status` endpoint [More info](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md). -* Sequence number in automatically loaded if it's not set in `/builder`. -* Fixed log levels in `PaymentListener` (#73). -* Fixed `AllowedFI` table name under Windows (#72). -* New `-v` parameter to print app version. - -## 0.0.26 - -* Fix log level in `PaymentListener`. - -## 0.0.25 - -* [XLM (lumen)](https://www.stellar.org/lumens/) payments can be now used in `PaymentListener`. -* Fixed a loop in `PaymentListener` occurring when multiple payments fail. - -## 0.0.24 - -* Better responses. -* Use `http.Client` with `Timeout`. - -## 0.0.23 - -* Fix a bug in `protocols.Asset.String`. Add more_info field to `invalid_parameter` errors. - -## 0.0.22 - -* Ability to reprocess received payments. - -## 0.0.21 - -* Add asset issuer to receive callback - -## 0.0.20 - -* Update `github.com/stellar/go` dependency. - -## 0.0.19 - -* Fix account ID destinations in /payment - -## 0.0.18 - -* Fixed `-config` param in bridge. - -## 0.0.17 - -* Added `-config` param to use custom config file -* Removed unused `EncryptionKey` config param -* Added use_compliance parameter in `/payment` to force using compliance protocol. - -## 0.0.16 - -* New version of compliance protocol. - -## 0.0.15 - -* Change stellar.toml location to new standard. - -## 0.0.14 - -* Bug fixes for postgres - -## 0.0.13 - -* Add `mac_key` configuration - -## 0.0.12 - -* Fix `inject` in compliance server. - -## 0.0.11 - -* `/create-keypair` endpoint, -* Sending routing information in receive callback. - -## 0.0.10 - -* Send only relevant data to compliance callbacks (#17). -* `hooks` are now called `callbacks` in `bridge` server. - -## 0.0.9 - -* Transaction builder (#14) - -## 0.0.8 - -* [Compliance protocol](https://www.stellar.org/developers/guides/compliance-protocol.html) support. -* Saving and reading memo preimage. -* This repo will now contain two apps: `bridge` (for building, submitting and monitoring transactions) and `compliance` (for Compliance protocol). Both are built in a single build process. Each app has it's own README file. -* Dependency injection is now done using [facebookgo/inject](https://godoc.org/github.com/facebookgo/inject). -* Handling and validation of requests and responses is now done in `protocols` package. This package contains methods for transforming `url.Values` from/to request structs and for marshalling responses. It also contains common errors (missing/invalid fields, internal server error, etc.) and all protocol-specific error responses. It also includes stellar.toml and federation resolving. -* New `net` and `server` packages that contain some helper network connected functions and structs. -* Improvements to `db` package. - -## 0.0.7 - -* Add path payments, -* Change `config.toml` file structure, -* Partial implementation of Compliance Protocol. - -## 0.0.6 - -* When there are no `ReceivePayment`s in database, payment listener will start with cursor `now`. -* Fix a bug in `db.Repository.GetLastCursorValue()`. - -## 0.0.5 - -* Add `MEMO_HASH` support. - -## 0.0.4 - -* Fixed bugs connected with running server using `postgres` DB (full refactoring of `db` package), -* Fixed starting a minimum server with a single endpoint: `/payment`. - -## 0.0.3 - -* Send `create_account` operation in `/payment` if account does not exist. -* Fixed major bug in `PaymentListener`. -* Sending to Stellar address with memo in `/send`. -* Standardized responses. -* Updated README file. - -## 0.0.2 - -* Added `/payment` endpoint. -* Now it's possible to start a server with parameter that are not required. Minimum version starts a server with a single endpoint: `/payment`. -* Added config parameters validation. -* Added `network_passphrase` config parameter. -* `postgres` migration files. -* Fixed sending to Stellar address. -* Fixed `horizon.AccountResponse.SequenceNumber` bug. -* Fixed minor bugs. -* Code refactoring. -* Added example config file to the release package -* Updated README file. - -## 0.0.1 - -* Initial release. diff --git a/services/compliance/README.md b/services/compliance/README.md deleted file mode 100644 index bf7b6dc6c1..0000000000 --- a/services/compliance/README.md +++ /dev/null @@ -1,304 +0,0 @@ -# compliance-server -This is a stand alone server written in go. It is designed to make [Compliance protocol](https://www.stellar.org/developers/guides/compliance-protocol.html) requests to other organizations. You can connect to it from the `bridge` server or any other server that can talk to it (check API section). - -## Downloading the server -[Prebuilt binaries](https://github.com/stellar/go/releases) of the compliance server are available on the [releases page](https://github.com/stellar/go/releases). - -| Platform | Binary file name | -|----------------|------------------------------------------------------------------------------------------| -| Mac OSX 64 bit | [compliance-vX.X.X-darwin-amd64](https://github.com/stellar/go/releases) | -| Linux 64 bit | [compliance-vX.X.X-linux-amd64](https://github.com/stellar/go/releases) | -| Windows 64 bit | [compliance-vX.X.X-windows-amd64.exe](https://github.com/stellar/go/releases) | - -Alternatively, you can [build](#building) the binary yourself. - -## Config - -The `compliance.cfg` file must be present in a working directory (you can load another file by using `-c` parameter). Here is an [example configuration file](https://github.com/stellar/go/blob/master/services/compliance/compliance_example.cfg). Config file should contain following values: - -* `external_port` - external server listening port (should be accessible from public) -* `internal_port` - internal server listening port (should be accessible from your internal network only!) -* `needs_auth` - set to `true` if you need to do sanctions check for payment receiver -* `network_passphrase` - passphrase of the network that will be used with this bridge server: - * test network: `Test SDF Network ; September 2015` - * public network: `Public Global Stellar Network ; September 2015` -* `database` - This database is used internally to store memo information and to keep track of what FIs have been authorized to receive customer info. - * `type` - database type (postgres) - * `url` - url to database connection. **IMPORTANT** The `compliance` server must not use the same database as the `bridge` server. - * for `postgres`: `postgres://user:password@host/dbname?sslmode=sslmode` ([more info](https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters)) -* `keys` - * `signing_seed` - The secret seed that will be used to sign messages. Public key derived from this secret key should be in your `stellar.toml` file. - * `encryption_key` - The secret key used to decrypt messages. _Not working yet._ -* `callbacks` - * `sanctions` - Callback that performs sanctions check. Read [Callbacks](#callbacks) section. - * `ask_user` - Callback that asks user for permission for reading their data. Read [Callbacks](#callbacks) section. - * `fetch_info` - Callback that returns user data. Read [Callbacks](#callbacks) section. - * `tx_status` - Callback that returns user data. Read [Callbacks](#callbacks) section. -* `tls` (only when running HTTPS external server) - * `certificate_file` - a file containing a certificate - * `private_key_file` - a file containing a matching private key -* `log_format` - set to `json` for JSON logs -* `tx_status_auth` - authentication credentials for `/tx_status` endpoint. - * `username` - * `password` - minimum 10 chars - -Check [`compliance_example.cfg`](./compliance_example.cfg). - -## Getting started - -After creating `compliance.cfg` file, you need to run DB migrations: -``` -./compliance --migrate-db -``` - -Then you can start the server: -``` -./compliance -``` - -## API - -`Content-Type` of requests data should be `application/x-www-form-urlencoded`. - -### POST :external_port/ (Auth endpoint) - -Process auth request from external organization sent before sending a payment. Check [Compliance protocol](https://www.stellar.org/developers/guides/compliance-protocol.html) for more info. It also saves memo preimage to the database. - -#### Request Parameters - -name | | description ---- | --- | --- -`data` | required | Auth data. -`sig` | required | Signature. - -Read more in [Compliance protocol](https://www.stellar.org/developers/guides/compliance-protocol.html#auth-server) doc. - -#### Response - -Returns [Auth response](https://www.stellar.org/developers/guides/compliance-protocol.html#server-response). - -### POST :internal_port/send - -Typically called by the bridge server when a user initiates a payment. This endpoint causes the compliance server to send an Auth request to another organization. It will call the Auth endpoint of the receiving instition. - -#### Request Parameters - -name | | description ---- | --- | --- -`id` | required | ID of the payment/transaction. In case of `pending` response or errors, you should resubmit the request with the same `id` value. -`source` | required | Account ID of transaction source account. -`sender` | required | Stellar address (ex. `bob*stellar.org`) of payment sender account. -`destination` | required | Account ID or Stellar address (ex. `bob*stellar.org`) of payment destination account -`amount` | required | Amount that destination will receive -`extra_memo` | optional | Additional information attached to memo preimage. -`asset_code` | optional | Asset code (XLM when empty) destination will receive -`asset_issuer` | optional | Account ID of asset issuer (XLM when empty) destination will receive -`send_max` | optional | [path_payment] Maximum amount of send_asset to send -`send_asset_code` | optional | [path_payment] Sending asset code (XLM when empty) -`send_asset_issuer` | optional | [path_payment] Account ID of sending asset issuer (XLM when empty) -`path[n][asset_code]` | optional | [path_payment] If the path isn't specified the bridge server will find the path for you. Asset code of `n`th asset on the path (XLM when empty, but empty parameter must be sent!) -`path[n][asset_issuer]` | optional | [path_payment] Account ID of `n`th asset issuer (XLM when empty, but empty parameter must be sent!) -`path[n+1][asset_code]` | optional | [path_payment] Asset code of `n+1`th asset on the path (XLM when empty, but empty parameter must be sent!) -`path[n+1][asset_issuer]` | optional | [path_payment] Account ID of `n+1`th asset issuer (XLM when empty, but empty parameter must be sent!) -... | ... | _Up to 5 assets in the path..._ - -#### Response - -Returns [`SendResponse`](). - -### POST :internal_port/receive - -Typically called by the bridge server when a payment comes in. It is used to check that the payment was authorized by this compliance server. The call will return a memo preimage in the payment was authorized. - -#### Request Parameters - -name | | description ---- | --- | --- -`memo` | required | Memo hash. - -#### Response - -Returns [`ReceiveResponse`](). - -### POST :internal_port/allow_access - -Allows access to users data for external user or FI. - -#### Request Parameters - -name | | description ---- | --- | --- -`name` | required | Name of the external FI. -`domain` | required | Domain of the external FI. -`public_key` | required | Public key of the external FI. -`user_id` | optional | If set, only this user will be allowed. - -#### Response - -Will response with `200 OK` if saved. Any other status is an error. - -### POST :internal_port/remove_access - -Allows access to users data for external user or FI. - -#### Request Parameters - -name | | description ---- | --- | --- -`domain` | required | Domain of the external FI. -`user_id` | optional | If set, only this user entry will be removed. - -#### Response - -Will response with `200 OK` if removed. Any other status is an error. - -## Callbacks - -The Compliance server will send callback `POST` request to URLs you define in the config file. `Content-Type` of requests data will be `application/x-www-form-urlencoded`. - -### `callbacks.sanctions` - -If set in the config file, this callback will be called when sanctions checks need to be performed. If not set the compliance server will act as if the sanction check passes. - -#### Request - -name | description ---- | --- -`sender` | Sender info JSON - -The customer information that is exchanged between FIs is flexible but the typical fields are: - -* Full Name -* Date of birth -* Physical address - -#### Response - -Respond with one of the following status codes: -* `200 OK` when sender/receiver is allowed and the payment should be processed, -* `202 Accepted` when your callback needs some time for processing, -* `400 Bad Request` when sender info is invalid. -* `403 Forbidden` when sender/receiver is denied. - -Any other status code will be considered an error. - -When `202 Accepted` is returned the response body should contain JSON object with `pending` field which represents the estimated number of seconds needed for processing. For example, the following response means to try the payment again in an hour. - -When `400 Bad Request` is returned the response body should contain JSON object with `error` field with error string. - -```json -{"pending": 3600} -``` - -### `callbacks.ask_user` - -If set in the config file, this callback will be called when the sender needs your customer KYC info to send a payment. If not set then the customer information won't be given to the other FI. - -#### Request - -name | description ---- | --- -`amount` | Payment amount -`asset_code` | Payment asset code -`asset_issuer` | Payment asset issuer -`sender` | Sender info JSON -`note` | Note attached to the payment - -The customer information (`sender`) that is exchanged between FIs is flexible but the typical fields are: - -* Full Name -* Date of birth -* Physical address - -#### Response - -Respond with one of the following status codes: -* `200 OK` when your customer has allowed sharing his/her compliance information with the requesting FI. -* `202 Accepted` when your callback needs some time for processing, ie to ask the customer. -* `400 Bad Request` when request data is invalid. -* `403 Forbidden` when your customer has denied sharing his/her compliance information with the requesting FI. - -Any other status code will be considered an error. - -When `202 Accepted` is returned the response body should contain JSON object with `pending` field which represents estimated number of seconds needed for processing. For example, the following response means to try the payment again in an hour. - -When `400 Bad Request` is returned the response body should contain JSON object with `error` field with error string. - -```json -{"pending": 3600} -``` - -### `callbacks.fetch_info` - -This callback should return the compliance information of your customer identified by `address`. - -#### Request - -name | description ---- | --- -`address` | Stellar address (ex. `alice*acme.com`) of the user. - -#### Response - -This callback should return `200 OK` status code and JSON object with the customer compliance info: - -```json -{ - "name": "John Doe", - "address": "User physical address", - "date_of_birth": "1990-01-01" -} -``` -### `callbacks.tx_status` -This callback should return the status of a transaction as explained in [`SEP-0004`](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0004.md). - -#### Request - -name | description ---- | --- -`id` | Stellar transaction ID. - -#### Response -This callback should return `200 OK` status code and JSON object with the transaction status info: - -```json -{ - "status": "status code as defined in SEP-0001", - "recv_code": "arbitrary string", - "refund_tx": "tx_hash", - "msg": "arbitrary string" -} -``` - - -Any other status code will be considered an error. - -## Building - -[gb](http://getgb.io) is used for building and testing. - -Given you have a running golang installation, you can build the server with: - -``` -gb build -``` - -After a successful build, you should find `bin/bridge` in the project directory. - -## Running tests - -``` -gb test -``` - -## Documentation - -``` -godoc -goroot=. -http=:6060 -``` - -Then simply open: -``` -http://localhost:6060/pkg/github.com/stellar/gateway/ -``` -in a browser. diff --git a/services/compliance/compliance_example.cfg b/services/compliance/compliance_example.cfg deleted file mode 100644 index 15dba21b27..0000000000 --- a/services/compliance/compliance_example.cfg +++ /dev/null @@ -1,28 +0,0 @@ -# Compliance server compliance.cfg example - -external_port = 8001 -internal_port = 8002 -needs_auth = false -network_passphrase = "Test SDF Network ; September 2015" - -[database] -type = "postgres" -url = "postgres://root@localhost/compliance?sslmode=disable" - -[keys] -# GC7DVHGMSQYAPYXQU652VVHEMZ2OZN4VH44T67QILDHDMBOACMZHQWLW -signing_seed = "SBEL63EBNQUTQ2ZTGHGLLXEMP6THALGS3VQ2N4RVHUWIBB5KGDJWVF3R" - -[callbacks] -sanctions = "http://sanctions" -ask_user = "http://ask_user" -fetch_info = "http://fetch_info" -tx_status = "http://tx_status" - -[tls] -certificate-file = "server.crt" -private-key-file = "server.key" - -#[tx_status_auth] -#username = "username" -#password = "password" diff --git a/services/compliance/internal/config/main.go b/services/compliance/internal/config/main.go deleted file mode 100644 index 5ff642f837..0000000000 --- a/services/compliance/internal/config/main.go +++ /dev/null @@ -1,129 +0,0 @@ -package config - -import ( - "errors" - "net/url" - - "github.com/stellar/go/keypair" - "github.com/stellar/go/support/config" -) - -// Config contains config params of the compliance server -type Config struct { - ExternalPort *int `valid:"required" toml:"external_port"` - InternalPort *int `valid:"required" toml:"internal_port"` - LogFormat string `valid:"optional" toml:"log_format"` - NeedsAuth bool `valid:"optional" toml:"needs_auth"` - NetworkPassphrase string `valid:"required" toml:"network_passphrase"` - Database Database `valid:"required"` - Keys Keys `valid:"required" toml:"keys"` - Callbacks Callbacks `valid:"optional" toml:"callbacks"` - TLS *config.TLS `valid:"optional"` - TxStatusAuth *TxStatusAuth `valid:"optional" toml:"tx_status_auth"` -} - -type TxStatusAuth struct { - Username string `valid:"required" toml:"username"` - Password string `valid:"required" toml:"password"` -} - -// Keys contains values of `keys` config group -type Keys struct { - SigningSeed string `valid:"required" toml:"signing_seed"` -} - -// Callbacks contains values of `callbacks` config group -type Callbacks struct { - Sanctions string `valid:"optional"` - AskUser string `valid:"optional" toml:"ask_user"` - FetchInfo string `valid:"optional" toml:"fetch_info"` - TxStatus string `valid:"optional" toml:"tx_status"` -} - -// Database contains values of `database` config group -type Database struct { - Type string `valid:"required"` - URL string `valid:"required"` -} - -// 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 == "" { - 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 - } - } - - _, err = url.Parse(c.Database.URL) - if err != nil { - err = errors.New("Cannot parse database.url param") - return - } - - switch c.Database.Type { - case "mysql": - err = errors.New("Invalid database.type param, mysql support is discontinued") - return - 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.TxStatus != "" { - _, err = url.Parse(c.Callbacks.TxStatus) - if err != nil { - err = errors.New("Cannot parse callbacks.tx_status 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 -} diff --git a/services/compliance/internal/config/main_test.go b/services/compliance/internal/config/main_test.go deleted file mode 100644 index e968f1cdf0..0000000000 --- a/services/compliance/internal/config/main_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package config - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestConfig_Validate_db_type(t *testing.T) { - c := Config{ - ExternalPort: func(i int) *int { return &i }(8001), - InternalPort: func(i int) *int { return &i }(8001), - LogFormat: "", - NetworkPassphrase: "Test SDF Network ; September 2015", - Database: Database{ - Type: "", - URL: "", - }, - Keys: Keys{ - SigningSeed: "SBEL63EBNQUTQ2ZTGHGLLXEMP6THALGS3VQ2N4RVHUWIBB5KGDJWVF3R", - }, - } - - testCases := []struct { - dbType string - wantErr error - }{ - {dbType: "", wantErr: errors.New("Invalid database.type param")}, - {dbType: "postgres", wantErr: nil}, - {dbType: "mysql", wantErr: errors.New("Invalid database.type param, mysql support is discontinued")}, - {dbType: "bogus", wantErr: errors.New("Invalid database.type param")}, - } - - for _, tc := range testCases { - t.Run(tc.dbType, func(t *testing.T) { - c.Database.Type = tc.dbType - err := c.Validate() - if tc.wantErr == nil { - assert.Nil(t, err) - } else { - require.NotNil(t, err) - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } - }) - } -} - -func TestConfig_Validate_db_url(t *testing.T) { - c := Config{ - ExternalPort: func(i int) *int { return &i }(8001), - InternalPort: func(i int) *int { return &i }(8001), - LogFormat: "", - NetworkPassphrase: "Test SDF Network ; September 2015", - Database: Database{ - Type: "postgres", - URL: "", - }, - Keys: Keys{ - SigningSeed: "SBEL63EBNQUTQ2ZTGHGLLXEMP6THALGS3VQ2N4RVHUWIBB5KGDJWVF3R", - }, - } - - testCases := []struct { - url string - wantErr error - }{ - {url: "", wantErr: nil}, - {url: "postgres://localhost/db", wantErr: nil}, - {url: " postgres:", wantErr: errors.New("Cannot parse database.url param")}, - } - - for _, tc := range testCases { - t.Run(tc.url, func(t *testing.T) { - c.Database.URL = tc.url - err := c.Validate() - if tc.wantErr == nil { - assert.Nil(t, err) - } else { - assert.Equal(t, tc.wantErr.Error(), err.Error()) - } - }) - } -} diff --git a/services/compliance/internal/crypto/sign.go b/services/compliance/internal/crypto/sign.go deleted file mode 100644 index 8cdde209c0..0000000000 --- a/services/compliance/internal/crypto/sign.go +++ /dev/null @@ -1,46 +0,0 @@ -package crypto - -import ( - "encoding/base64" - - "github.com/stellar/go/keypair" -) - -// SignerVerifierInterface is the interface that helps mocking SignerVerifier -type SignerVerifierInterface interface { - Sign(secretSeed string, message []byte) (string, error) - Verify(publicKey string, message, signature []byte) error -} - -// SignerVerifier implements methods to Sign and Verify signatures -type SignerVerifier struct{} - -// 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 -} - -// 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 -} diff --git a/services/compliance/internal/db/bindata.go b/services/compliance/internal/db/bindata.go deleted file mode 100644 index 76ea96a2a2..0000000000 --- a/services/compliance/internal/db/bindata.go +++ /dev/null @@ -1,342 +0,0 @@ -// Code generated by go-bindata. DO NOT EDIT. -// sources: -// latest.sql (7.872kB) -// migrations/01_init.sql (992B) -// migrations/02_auth_data.sql (272B) -// migrations/03_table_names.sql (436B) - -package db - -import ( - "bytes" - "compress/gzip" - "crypto/sha256" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "time" -) - -func bindataRead(data []byte, name string) ([]byte, error) { - gz, err := gzip.NewReader(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) - } - - var buf bytes.Buffer - _, err = io.Copy(&buf, gz) - clErr := gz.Close() - - if err != nil { - return nil, fmt.Errorf("read %q: %w", name, err) - } - if clErr != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -type asset struct { - bytes []byte - info os.FileInfo - digest [sha256.Size]byte -} - -type bindataFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time -} - -func (fi bindataFileInfo) Name() string { - return fi.name -} -func (fi bindataFileInfo) Size() int64 { - return fi.size -} -func (fi bindataFileInfo) Mode() os.FileMode { - return fi.mode -} -func (fi bindataFileInfo) ModTime() time.Time { - return fi.modTime -} -func (fi bindataFileInfo) IsDir() bool { - return false -} -func (fi bindataFileInfo) Sys() interface{} { - return nil -} - -var _latestSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x59\x5b\x73\xda\xc8\x12\x7e\x3e\xfc\x8a\x7e\xc3\xae\x23\x38\xe0\x18\x27\x81\xf2\x83\x02\xf2\x09\xb5\x20\x1c\x24\x36\x76\xd5\x56\x4d\x0d\xd2\x48\x4c\x59\x37\x8f\x86\x38\xe4\xd7\x6f\x8d\x04\xba\x21\x81\xbc\x68\xf3\x88\xa6\xa7\xfb\xeb\xab\xbe\x16\x9d\x4e\xab\xd3\x81\x47\x3f\xe4\x36\x23\xda\xb7\x19\x98\x98\xe3\x35\x0e\x09\x98\x5b\x37\x68\x75\x3a\x2d\x71\x3e\xd9\xba\x01\x31\xc1\x62\xbe\x9b\x0a\xfc\x20\x2c\xa4\xbe\x07\x9f\xbb\x77\xdd\x7e\x46\x6a\xbd\x83\xc0\x46\xe2\x7a\x41\xa4\xa5\x29\x3a\x84\x1c\x73\xe2\x12\x8f\x23\x4e\x5d\xe2\x6f\x39\xdc\x43\x6f\x14\x1d\x39\xbe\xf1\x72\xfc\x94\x9a\x0e\x41\xd4\x43\x9c\x61\x2f\xc4\x06\xa7\xbe\x87\x42\x12\x0a\xbd\xc7\xc2\x86\x43\x85\x6a\xe2\x19\xbe\x49\x3d\x1b\xee\xa1\xbd\xd2\x1f\x3e\xb5\x47\x07\xdb\x9e\x89\x99\x89\x0c\xdf\xb3\x7c\xe6\x52\xcf\x46\x21\x67\xd4\xb3\x43\xb8\x07\xdf\xdb\xeb\xd8\x10\xe3\x05\x59\x5b\x2f\xb6\xb5\xf6\x4d\x4a\xc4\xb9\x85\x9d\x90\xe4\xcc\xb8\xd4\x43\x2e\x09\x43\x6c\x47\x02\x6f\x98\x79\xd4\xb3\x63\x11\xe6\xbf\xa1\x90\x18\x5b\x46\xf9\x4e\x28\xb7\xac\x91\x08\xa5\x88\x93\x8a\x5d\x32\x84\xc0\x09\xec\xf0\xd5\x19\x81\xbe\x0b\xc8\x10\x94\x27\x5d\x51\xb5\xe9\x42\x1d\x81\x66\x6c\x88\x8b\x87\xd0\x19\xc1\xe2\xcd\x23\x6c\x08\x51\x1e\xc6\x4b\x45\xd6\x95\x54\x10\xa6\x0f\xa0\x2e\x74\x50\x9e\xa6\x9a\xae\x1d\xf4\xc1\xf7\xa9\xfe\x15\xb4\xf1\x57\x65\x2e\x8b\x3c\x18\x98\x63\xc7\xb7\x47\xad\xbc\xf5\x54\x4b\x01\xc7\x78\x31\x9f\x2b\xaa\x5e\x8d\x22\x3e\x87\x85\x7a\xac\x03\xa6\x1a\xb4\x1f\x67\xff\x0b\x6c\x51\x49\x01\xf3\x0d\x62\x6e\x19\x76\xc0\xc1\x9e\xbd\xc5\x36\x69\x0b\x18\x51\x26\x08\x66\xc6\x06\x05\x98\x6f\xe0\x1e\x82\xed\xda\xa1\x86\x94\x87\x2b\xc4\x4c\x62\xe1\xad\xc3\x11\xc7\x6b\x87\x84\x01\x36\x88\xc8\x68\xbb\x70\xfa\x46\xf9\x06\xf9\xd4\xcc\x24\x29\xe7\x2b\x76\x1c\xff\x8d\x98\xc8\xa2\x07\x27\x75\xf9\xcb\x4c\x49\x5d\x8c\xed\x27\x7e\x32\xdf\xe7\xd9\x88\x47\xd2\x19\x2d\x70\xd5\x02\x00\xa0\x26\xac\xa9\x4d\x3d\x1e\x65\x41\x5d\xcd\x66\x52\xf4\xdc\xc3\x2e\x01\x63\x83\x19\x36\x38\x61\xf0\x03\xb3\x1d\xf5\xec\xab\x9b\xc1\xe0\xba\x20\x69\xfa\x2e\xa6\x5e\x3d\xd9\x18\x23\x7a\x21\xbb\x54\xfe\x6a\x70\x57\x14\x3b\xa0\xc4\x1c\x44\x73\x84\x1c\xbb\x01\x88\x00\x89\x36\x11\x4f\xe0\x97\xef\x91\xe4\x52\xeb\x5a\x64\x44\x9e\xe9\xca\xf2\xd8\xcd\xc5\x77\x55\x3c\x5e\x44\x01\xa9\x88\xe9\x36\x24\xac\x89\xa8\x0a\x3d\x67\xe2\x6a\x51\x54\x3f\xb4\x16\x45\xef\x89\xae\x45\x51\xcd\x00\x0b\xa0\x88\x9a\xf5\xd4\x36\x96\x8d\x28\x3c\x35\xf2\x61\x51\x44\x4d\x14\x92\xd7\x43\x4e\x34\xe5\xdb\x4a\x51\xc7\xf5\xd3\x72\xb8\x70\xa4\x31\x72\x48\xd3\xe5\xa5\x1e\x8f\x98\x7e\xf4\x60\xaa\x8e\x97\x4a\x34\x10\xbe\x3c\xef\x1f\xa9\x0b\x98\x4f\xd5\x3f\xe5\xd9\x4a\x49\x7e\xcb\x4f\xe9\xef\xb1\x3c\xfe\xaa\x40\xbf\xc2\xd5\xc4\xde\x45\xee\x46\x97\x27\xf0\xe5\xf9\xbc\xdf\x31\x86\x4a\xb7\x13\x4d\x99\xce\xe8\x52\xb3\x38\x4f\xf7\x87\xfb\xea\x68\x34\x03\x19\x9d\xbf\x29\x07\x19\x8b\x75\xb2\x70\xc2\xe9\x8b\xf3\x50\x84\x92\xcb\x84\x38\x2c\xcb\xc5\x96\x6f\x90\xa0\x29\x17\x0d\xa6\x83\x92\x33\x53\x89\x91\xd7\x2d\x09\x79\xed\x91\xf0\x9e\xa9\x94\x62\xe0\xe4\x27\x3f\x35\x26\x12\xc1\x93\xe9\xda\xf2\x8d\x10\x6a\xae\x40\xf3\x0a\xff\xf5\xea\xcc\x9b\xbb\xc4\xd7\x0b\xea\xb2\x04\x44\x5c\x94\x87\x1c\x54\x54\xa4\xcf\xe8\x2f\x62\x66\x69\xec\xa5\xe5\x79\xac\xf1\x4c\xad\x66\x29\x74\x69\xbd\xde\xdd\x16\x4b\xd0\x25\xae\x5f\x4b\x30\xab\xfb\xa7\xc9\xf2\x15\x9b\x96\xf3\x1e\x73\xcd\x17\xe2\xbe\x65\x6a\x76\x40\x49\x3c\xce\x95\x48\x7c\x25\x1f\x97\xe6\x7a\xa3\x4a\xfb\xef\x68\x94\x2a\xdb\x8d\x85\xe4\xb2\x16\x3a\x09\x2f\xe9\xa7\xe3\x8c\x96\x34\x97\xed\xb3\x00\xb9\xd4\x66\x58\x08\x84\x97\x74\x55\x41\x55\xda\x4e\x65\xd5\x1c\x04\x0e\x2d\x2b\xe5\xb4\x8e\x8f\xcb\xb4\x68\xa0\x0e\xc5\xb6\x28\x50\xf3\xe0\xd5\x44\x79\x90\x57\x33\xbd\x6e\xcc\x63\xab\x0b\x75\x96\xa5\x2f\x10\x9f\x8d\x17\xb3\xd5\x5c\x15\xde\x89\x65\x6a\xaf\x18\x3c\xf2\x93\xff\xc0\xce\x55\xbb\x48\x84\xda\xc3\x21\x23\xb6\xe1\xe0\x30\xbc\xae\x60\x3f\x31\x4d\x6d\x12\x6c\xa4\xf0\x3d\x70\x33\x7c\xe1\x24\xe0\xe4\x85\xd9\x08\xda\x44\x5b\x3d\xa8\xf9\x57\xc8\x39\x9c\x25\x63\xad\x29\xd0\x25\xaa\x6b\x7b\x50\xd5\xc1\xe5\xee\x4c\x44\x74\x2c\x9f\x9d\x5e\xc9\x61\x22\xeb\x72\x8d\x9e\x5d\x3c\xe6\xea\xf9\x8a\x9a\x52\xb4\x76\x4b\x7b\x7a\x25\x65\xd6\x65\x29\xb3\x85\x5d\xc3\xc3\x72\x31\x87\x90\x9b\xd4\x1b\xb5\xfe\xea\x9e\x83\x77\xb4\xdd\xfe\x13\x80\xf1\x6a\x2b\x20\xee\x37\x58\x29\xdd\x4e\xa5\xfc\xe6\x29\x1d\xd6\xcb\x5a\xa0\x6b\xae\x42\x9a\x52\xa3\x3c\x34\x65\xa6\x8c\xf5\xcc\x17\x98\x6e\x48\x2a\xe6\x80\x04\x7d\x29\xfe\xce\x52\x35\x07\x4e\x2d\x04\xcd\xa0\xc9\xb6\x79\x29\x9e\x62\x42\x4b\x57\x82\x77\x65\x33\xdd\x07\x44\x7a\x52\xda\x9f\xd6\x5c\x22\x72\x2e\x63\x67\xb8\xe9\xa5\x21\x2a\x8c\x97\xba\xf1\x39\x43\x50\xdf\x1b\xac\x32\x76\x2a\xa2\x95\x9f\x18\x52\xc4\x30\xa5\x22\x7d\x94\xf2\x64\x51\x82\x9a\x61\xad\x4d\x5e\x1a\x88\x71\xe5\x00\xac\x13\xf0\x53\xa4\xe5\x3d\x91\x3e\x62\x2c\xd1\xec\x48\xa8\x49\x3e\x64\xbd\x3e\xa2\x1e\xe5\xdd\xf0\xd5\xf9\xcf\x4d\xaf\xff\xa9\xd3\xbb\xeb\xdc\x7c\x86\xfe\x60\xd8\xbf\x19\x0e\x06\xdd\xc1\xe7\xdb\xc1\xc7\x4f\xff\xed\xdd\xb4\x7a\x37\x28\xdd\x67\xaa\xc4\xef\x7a\xfd\x8f\x83\x0f\x91\xf8\x87\xf8\x13\x6d\x34\xde\xc2\x13\x17\x6e\x3f\x0e\x6e\xc5\x85\xaa\x29\x26\xa6\x79\x3a\x70\x82\x17\xb2\x4b\xbf\x4b\xab\x9a\xbe\x94\xa7\xea\x65\xdc\x27\xa2\x6e\xf2\x64\x92\xd1\x57\x30\x08\x8f\xcb\xe9\x5c\x5e\x3e\xc3\x1f\xca\xb3\x08\xe7\x69\xb2\x93\x9d\x47\x8d\xc3\x15\x5a\x4f\x00\x4e\x8c\x9e\x87\x9c\x0c\xaf\x64\x38\x34\x07\xf6\xa0\xbb\x14\x69\xd6\x5c\x2d\x98\x25\x63\xa3\xbc\xd9\x1a\x75\xe0\xd8\x6a\x95\x37\xe5\x40\xce\xba\x56\xec\xd3\xc2\xef\xe6\xbc\x29\x28\x2e\x73\xa3\xcc\xf6\xf9\xdc\x58\x14\xad\x77\x7b\xda\x72\x40\x3a\x55\x27\xca\x53\xed\x1d\x6b\xa5\x4e\xbf\xad\x94\xf8\x52\x5e\x1f\x2c\xd4\xec\x04\x58\x69\x53\xf5\xff\xb0\xe6\x8c\x10\xb8\x8a\x25\xaa\xf0\xa4\xdc\xa9\x39\x4c\x99\x7f\x02\x4e\xe0\x4a\xa5\x4a\xea\x58\xa8\xc9\x31\x3b\xb4\x67\x2c\x4d\xa0\xac\xd6\x9e\xc5\x1b\x8d\xa7\x1c\xe2\x72\xaa\x79\x84\x3e\xa5\x36\x0d\x80\xcd\x7c\x1e\x15\xd8\x92\x39\x94\x03\x96\x0a\x65\xc0\x54\xfd\x27\x0d\x86\xef\x06\x0e\xe1\x24\xb2\xf9\x77\x00\x00\x00\xff\xff\x3c\x3d\xcd\xce\xc0\x1e\x00\x00") - -func latestSqlBytes() ([]byte, error) { - return bindataRead( - _latestSql, - "latest.sql", - ) -} - -func latestSql() (*asset, error) { - bytes, err := latestSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "latest.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe6, 0xa3, 0x40, 0x86, 0x73, 0xaf, 0xcc, 0xe4, 0xb8, 0x1a, 0x9e, 0xfd, 0x6f, 0xed, 0xe9, 0xe2, 0x34, 0x9d, 0xac, 0x24, 0x6d, 0xf5, 0xe4, 0xd8, 0xb, 0xca, 0xe8, 0xf9, 0x3f, 0xe1, 0x73, 0x36}} - return a, nil -} - -var _migrations01_initSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x93\x41\x6f\x82\x40\x10\x85\xef\xfb\x2b\xe6\x28\xa9\x5e\x9a\xea\x85\x13\xad\x34\x21\xb5\x68\x09\x24\xf5\xb4\x19\xdd\x45\x27\x65\xc1\x2c\x4b\xd5\xfe\xfa\x86\x5a\x85\xad\xa2\xe9\x75\xdf\xdb\x99\xf7\x3e\xd8\xc1\x00\xee\x14\xad\x34\x1a\x09\xc9\x86\x3d\x45\xbe\x17\xfb\x10\x7b\x8f\x13\x1f\xbc\xca\xac\x0b\x4d\x5f\x52\xc4\x1a\xf3\x12\x97\x86\x8a\x1c\x7a\x0c\x80\x04\x2c\x68\x55\x4a\x4d\x98\xf5\x19\x80\x69\x74\x4e\x02\x3e\x51\x2f\xd7\xa8\x7b\xa3\x07\x07\xc2\x69\x0c\x61\x32\x99\xd4\x36\x25\x55\xd1\x29\xb6\x67\xec\x84\x06\x23\x77\xc6\x32\xe0\x29\x0e\x47\x03\x86\x94\x2c\x0d\xaa\x8d\xe5\x11\x68\xf0\xfc\x26\x03\x98\x45\xc1\xab\x17\xcd\xe1\xc5\x9f\x43\x8f\x84\xc3\x1c\x97\xfd\x69\x9b\x65\xc5\x56\x8a\xe7\xe0\x62\xc3\x1c\x95\x3c\x45\xbf\x1f\x0e\xed\xec\xa2\x50\x48\x79\xb7\xbe\xa9\x16\x19\x2d\xf9\x87\xdc\xc3\x8f\x61\x38\xb2\x75\x3c\xec\xee\xee\x75\x16\x9f\x39\xd0\x14\x48\xc2\xe0\x2d\xf1\x21\x08\xc7\xfe\x3b\x60\x4a\x7c\xb1\xe7\xbf\x91\xa6\x61\xbb\xd8\xe1\xd0\x71\xaf\x5d\x6c\x65\xb5\x2f\x37\x42\x17\xbb\xa4\x94\xfa\x22\xbd\x94\xf8\x75\x80\x29\xf1\x5b\x0c\x53\xe2\xb7\x30\x56\xa5\xd4\xed\xff\xef\x6c\xc6\xff\x39\x3b\x5d\x94\xab\x9a\x95\x95\x89\x1f\xd7\x37\xd8\x0e\x40\x2c\x57\xff\x98\xb2\x9e\xcc\xda\xcf\x6f\x5c\x6c\x73\x36\x8e\xa6\xb3\x6b\xcf\xcf\xb5\x1c\xc7\x8f\x73\xe9\xb4\xde\xed\xb2\xef\x00\x00\x00\xff\xff\x02\xc5\x23\x8a\xe0\x03\x00\x00") - -func migrations01_initSqlBytes() ([]byte, error) { - return bindataRead( - _migrations01_initSql, - "migrations/01_init.sql", - ) -} - -func migrations01_initSql() (*asset, error) { - bytes, err := migrations01_initSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/01_init.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1c, 0x1f, 0x65, 0xdd, 0x6e, 0xc, 0xa7, 0x34, 0x47, 0xb6, 0x79, 0x15, 0xe3, 0x89, 0x5, 0xce, 0xb2, 0x6d, 0x6a, 0xc3, 0xc3, 0xb7, 0xcd, 0x41, 0xe3, 0x70, 0x61, 0x88, 0x67, 0xe2, 0x14, 0x3e}} - return a, nil -} - -var _migrations02_auth_dataSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x8f\xb1\x4f\x85\x30\x10\x87\xf7\xfb\x2b\x7e\x23\x44\xdf\x62\xf2\x26\xa6\x6a\x3b\x10\xb1\x60\x43\x13\x99\xc8\x69\x1b\x68\x22\xa0\xa5\xa8\x7f\xbe\x61\x30\xa2\xc9\x5b\xef\xbb\xcb\x7d\xdf\xe9\x84\xab\x29\x0c\x91\x93\x87\x7d\xa3\x3b\xa3\x44\xab\xd0\x8a\xdb\x4a\x41\x6c\x69\x94\x9c\x18\x19\x01\xc1\xe1\x39\x0c\xab\x8f\x81\x5f\xaf\x09\x88\xfe\x7d\xf3\x6b\xea\x83\xc3\x07\xc7\x97\x91\x63\x76\x73\x3e\xe7\xd0\x75\x0b\x6d\xab\x6a\xdf\x71\xcb\xc4\x61\xbe\xcc\x79\x4b\x63\xef\xf6\x0f\xc9\x7f\xa5\x3f\x88\x80\xc6\x94\x0f\xc2\x74\xb8\x57\x1d\xb2\xe0\x72\xca\x0b\xfa\x11\xb4\xba\x7c\xb4\x0a\xa5\x96\xea\xe9\xa8\x52\xeb\x83\xf5\xef\x7c\xbf\x3c\x96\xca\xe5\x73\x26\x69\xea\xe6\x5f\x69\x41\xdf\x01\x00\x00\xff\xff\x0e\x07\x5f\x45\x10\x01\x00\x00") - -func migrations02_auth_dataSqlBytes() ([]byte, error) { - return bindataRead( - _migrations02_auth_dataSql, - "migrations/02_auth_data.sql", - ) -} - -func migrations02_auth_dataSql() (*asset, error) { - bytes, err := migrations02_auth_dataSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/02_auth_data.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x44, 0xe, 0xd, 0x9d, 0x9f, 0xc7, 0x29, 0x66, 0x52, 0x32, 0x41, 0x21, 0xaf, 0x86, 0xc0, 0x7e, 0x2d, 0xa9, 0x63, 0xfc, 0x67, 0x55, 0xc5, 0xca, 0xff, 0xe8, 0xca, 0x7d, 0x6a, 0x4c, 0x84, 0xaf}} - return a, nil -} - -var _migrations03_table_namesSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x6c\x8f\xd1\x0a\xc2\x20\x14\x86\xef\xf7\x14\xe7\x3e\xf6\x04\x5e\x19\x33\x08\x56\xc1\x70\xd7\xe3\x90\x56\xc2\xd2\xd0\x23\x83\x9e\x3e\x2a\x6a\xba\x79\xab\xff\xff\x9f\xef\xab\x6b\xd8\xdc\xcd\xd5\x23\x69\xe8\x1f\x15\x6f\xa5\xe8\x40\xf2\x6d\x2b\x80\x47\xba\x39\x6f\x9e\x5a\x49\x8f\x36\xe0\x99\x8c\xb3\xd0\x89\x23\x3f\x08\x90\x27\xc0\xff\xff\x40\x73\x80\xe5\x1b\xe3\xe8\x26\xad\x76\xfb\xb4\xf7\x7d\x1b\x2e\xa6\x98\xed\x83\xf6\x85\x74\x0c\xda\xb3\x15\x5f\x83\x84\x0b\xa4\x41\x21\x21\xab\xaa\xd4\xac\x71\x93\xcd\xba\x65\xf6\x64\xa9\x28\x9f\xdf\x9f\x3d\xd2\xde\x4f\xb8\x9c\x8d\xb9\x5c\xa2\xcc\x56\x7c\x1f\x91\x05\xd2\xdb\x97\xbd\x02\x00\x00\xff\xff\x6b\xd7\x46\x9f\xb4\x01\x00\x00") - -func migrations03_table_namesSqlBytes() ([]byte, error) { - return bindataRead( - _migrations03_table_namesSql, - "migrations/03_table_names.sql", - ) -} - -func migrations03_table_namesSql() (*asset, error) { - bytes, err := migrations03_table_namesSqlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "migrations/03_table_names.sql", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd7, 0x1d, 0xf4, 0xb6, 0xa, 0xf9, 0xd2, 0x5b, 0xde, 0xed, 0xda, 0x7, 0xef, 0x14, 0x75, 0x5d, 0xc3, 0xa6, 0xa5, 0xfc, 0x57, 0x63, 0xcc, 0x25, 0xe0, 0x1a, 0x46, 0x57, 0x87, 0x74, 0xfc, 0x8}} - return a, nil -} - -// Asset loads and returns the asset for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func Asset(name string) ([]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) - } - return a.bytes, nil - } - return nil, fmt.Errorf("Asset %s not found", name) -} - -// AssetString returns the asset contents as a string (instead of a []byte). -func AssetString(name string) (string, error) { - data, err := Asset(name) - return string(data), err -} - -// MustAsset is like Asset but panics when Asset would return an error. -// It simplifies safe initialization of global variables. -func MustAsset(name string) []byte { - a, err := Asset(name) - if err != nil { - panic("asset: Asset(" + name + "): " + err.Error()) - } - - return a -} - -// MustAssetString is like AssetString but panics when Asset would return an -// error. It simplifies safe initialization of global variables. -func MustAssetString(name string) string { - return string(MustAsset(name)) -} - -// AssetInfo loads and returns the asset info for the given name. -// It returns an error if the asset could not be found or -// could not be loaded. -func AssetInfo(name string) (os.FileInfo, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) - } - return a.info, nil - } - return nil, fmt.Errorf("AssetInfo %s not found", name) -} - -// AssetDigest returns the digest of the file with the given name. It returns an -// error if the asset could not be found or the digest could not be loaded. -func AssetDigest(name string) ([sha256.Size]byte, error) { - canonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[canonicalName]; ok { - a, err := f() - if err != nil { - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) - } - return a.digest, nil - } - return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) -} - -// Digests returns a map of all known files and their checksums. -func Digests() (map[string][sha256.Size]byte, error) { - mp := make(map[string][sha256.Size]byte, len(_bindata)) - for name := range _bindata { - a, err := _bindata[name]() - if err != nil { - return nil, err - } - mp[name] = a.digest - } - return mp, nil -} - -// AssetNames returns the names of the assets. -func AssetNames() []string { - names := make([]string, 0, len(_bindata)) - for name := range _bindata { - names = append(names, name) - } - return names -} - -// _bindata is a table, holding each asset generator, mapped to its name. -var _bindata = map[string]func() (*asset, error){ - "latest.sql": latestSql, - "migrations/01_init.sql": migrations01_initSql, - "migrations/02_auth_data.sql": migrations02_auth_dataSql, - "migrations/03_table_names.sql": migrations03_table_namesSql, -} - -// AssetDir returns the file names below a certain -// directory embedded in the file by go-bindata. -// For example if you run go-bindata on data/... and data contains the -// following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png -// then AssetDir("data") would return []string{"foo.txt", "img"}, -// AssetDir("data/img") would return []string{"a.png", "b.png"}, -// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and -// AssetDir("") will return []string{"data"}. -func AssetDir(name string) ([]string, error) { - node := _bintree - if len(name) != 0 { - canonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(canonicalName, "/") - for _, p := range pathList { - node = node.Children[p] - if node == nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - } - } - if node.Func != nil { - return nil, fmt.Errorf("Asset %s not found", name) - } - rv := make([]string, 0, len(node.Children)) - for childName := range node.Children { - rv = append(rv, childName) - } - return rv, nil -} - -type bintree struct { - Func func() (*asset, error) - Children map[string]*bintree -} - -var _bintree = &bintree{nil, map[string]*bintree{ - "latest.sql": &bintree{latestSql, map[string]*bintree{}}, - "migrations": &bintree{nil, map[string]*bintree{ - "01_init.sql": &bintree{migrations01_initSql, map[string]*bintree{}}, - "02_auth_data.sql": &bintree{migrations02_auth_dataSql, map[string]*bintree{}}, - "03_table_names.sql": &bintree{migrations03_table_namesSql, map[string]*bintree{}}, - }}, -}} - -// RestoreAsset restores an asset under the given directory. -func RestoreAsset(dir, name string) error { - data, err := Asset(name) - if err != nil { - return err - } - info, err := AssetInfo(name) - if err != nil { - return err - } - err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) - if err != nil { - return err - } - err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) - if err != nil { - return err - } - return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) -} - -// RestoreAssets restores an asset under the given directory recursively. -func RestoreAssets(dir, name string) error { - children, err := AssetDir(name) - // File - if err != nil { - return RestoreAsset(dir, name) - } - // Dir - for _, child := range children { - err = RestoreAssets(dir, filepath.Join(name, child)) - if err != nil { - return err - } - } - return nil -} - -func _filePath(dir, name string) string { - canonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) -} diff --git a/services/compliance/internal/db/latest.sql b/services/compliance/internal/db/latest.sql deleted file mode 100644 index 5bdb634e88..0000000000 --- a/services/compliance/internal/db/latest.sql +++ /dev/null @@ -1,364 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 9.6.1 --- Dumped by pg_dump version 9.6.1 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SET check_function_bodies = false; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: --- - -CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; - - --- --- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; - - -SET search_path = public, pg_catalog; - -SET default_tablespace = ''; - -SET default_with_oids = false; - --- --- Name: allowed_fi; Type: TABLE; Schema: public; Owner: root --- - -CREATE TABLE allowed_fi ( - id bigint NOT NULL, - name character varying(255) NOT NULL, - domain character varying(255) NOT NULL, - public_key character(56) NOT NULL, - allowed_at timestamp without time zone NOT NULL -); - - -ALTER TABLE allowed_fi OWNER TO root; - --- --- Name: allowed_user; Type: TABLE; Schema: public; Owner: root --- - -CREATE TABLE allowed_user ( - id bigint NOT NULL, - fi_name character varying(255) NOT NULL, - fi_domain character varying(255) NOT NULL, - fi_public_key character(56) NOT NULL, - user_id character varying(255) NOT NULL, - allowed_at timestamp without time zone NOT NULL -); - - -ALTER TABLE allowed_user OWNER TO root; - --- --- Name: allowedfi_id_seq; Type: SEQUENCE; Schema: public; Owner: root --- - -CREATE SEQUENCE allowedfi_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE allowedfi_id_seq OWNER TO root; - --- --- Name: allowedfi_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: root --- - -ALTER SEQUENCE allowedfi_id_seq OWNED BY allowed_fi.id; - - --- --- Name: alloweduser_id_seq; Type: SEQUENCE; Schema: public; Owner: root --- - -CREATE SEQUENCE alloweduser_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE alloweduser_id_seq OWNER TO root; - --- --- Name: alloweduser_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: root --- - -ALTER SEQUENCE alloweduser_id_seq OWNED BY allowed_user.id; - - --- --- Name: auth_data; Type: TABLE; Schema: public; Owner: root --- - -CREATE TABLE auth_data ( - id bigint NOT NULL, - request_id character varying(255) NOT NULL, - domain character varying(255) NOT NULL, - auth_data text NOT NULL -); - - -ALTER TABLE auth_data OWNER TO root; - --- --- Name: authdata_id_seq; Type: SEQUENCE; Schema: public; Owner: root --- - -CREATE SEQUENCE authdata_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE authdata_id_seq OWNER TO root; - --- --- Name: authdata_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: root --- - -ALTER SEQUENCE authdata_id_seq OWNED BY auth_data.id; - - --- --- Name: authorized_transaction; Type: TABLE; Schema: public; Owner: root --- - -CREATE TABLE authorized_transaction ( - id bigint NOT NULL, - transaction_id character varying(64) NOT NULL, - memo character varying(64) NOT NULL, - transaction_xdr text NOT NULL, - authorized_at timestamp without time zone NOT NULL, - data text NOT NULL -); - - -ALTER TABLE authorized_transaction OWNER TO root; - --- --- Name: authorizedtransaction_id_seq; Type: SEQUENCE; Schema: public; Owner: root --- - -CREATE SEQUENCE authorizedtransaction_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER TABLE authorizedtransaction_id_seq OWNER TO root; - --- --- Name: authorizedtransaction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: root --- - -ALTER SEQUENCE authorizedtransaction_id_seq OWNED BY authorized_transaction.id; - - --- --- Name: gorp_migrations; Type: TABLE; Schema: public; Owner: root --- - -CREATE TABLE gorp_migrations ( - id text NOT NULL, - applied_at timestamp with time zone -); - - -ALTER TABLE gorp_migrations OWNER TO root; - --- --- Name: allowed_fi id; Type: DEFAULT; Schema: public; Owner: root --- - -ALTER TABLE ONLY allowed_fi ALTER COLUMN id SET DEFAULT nextval('allowedfi_id_seq'::regclass); - - --- --- Name: allowed_user id; Type: DEFAULT; Schema: public; Owner: root --- - -ALTER TABLE ONLY allowed_user ALTER COLUMN id SET DEFAULT nextval('alloweduser_id_seq'::regclass); - - --- --- Name: auth_data id; Type: DEFAULT; Schema: public; Owner: root --- - -ALTER TABLE ONLY auth_data ALTER COLUMN id SET DEFAULT nextval('authdata_id_seq'::regclass); - - --- --- Name: authorized_transaction id; Type: DEFAULT; Schema: public; Owner: root --- - -ALTER TABLE ONLY authorized_transaction ALTER COLUMN id SET DEFAULT nextval('authorizedtransaction_id_seq'::regclass); - - --- --- Data for Name: allowed_fi; Type: TABLE DATA; Schema: public; Owner: root --- - -COPY allowed_fi (id, name, domain, public_key, allowed_at) FROM stdin; -\. - - --- --- Data for Name: allowed_user; Type: TABLE DATA; Schema: public; Owner: root --- - -COPY allowed_user (id, fi_name, fi_domain, fi_public_key, user_id, allowed_at) FROM stdin; -\. - - --- --- Name: allowedfi_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root --- - -SELECT pg_catalog.setval('allowedfi_id_seq', 1, false); - - --- --- Name: alloweduser_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root --- - -SELECT pg_catalog.setval('alloweduser_id_seq', 1, false); - - --- --- Data for Name: auth_data; Type: TABLE DATA; Schema: public; Owner: root --- - -COPY auth_data (id, request_id, domain, auth_data) FROM stdin; -\. - - --- --- Name: authdata_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root --- - -SELECT pg_catalog.setval('authdata_id_seq', 1, false); - - --- --- Data for Name: authorized_transaction; Type: TABLE DATA; Schema: public; Owner: root --- - -COPY authorized_transaction (id, transaction_id, memo, transaction_xdr, authorized_at, data) FROM stdin; -\. - - --- --- Name: authorizedtransaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root --- - -SELECT pg_catalog.setval('authorizedtransaction_id_seq', 1, false); - - --- --- Data for Name: gorp_migrations; Type: TABLE DATA; Schema: public; Owner: root --- - -COPY gorp_migrations (id, applied_at) FROM stdin; -01_init.sql 2018-06-29 15:12:55.594578+02 -02_auth_data.sql 2018-06-29 15:12:55.601753+02 -03_table_names.sql 2018-06-29 15:12:55.604754+02 -\. - - --- --- Name: allowed_fi allowedfi_pkey; Type: CONSTRAINT; Schema: public; Owner: root --- - -ALTER TABLE ONLY allowed_fi - ADD CONSTRAINT allowedfi_pkey PRIMARY KEY (id); - - --- --- Name: allowed_user alloweduser_pkey; Type: CONSTRAINT; Schema: public; Owner: root --- - -ALTER TABLE ONLY allowed_user - ADD CONSTRAINT alloweduser_pkey PRIMARY KEY (id); - - --- --- Name: auth_data authdata_pkey; Type: CONSTRAINT; Schema: public; Owner: root --- - -ALTER TABLE ONLY auth_data - ADD CONSTRAINT authdata_pkey PRIMARY KEY (id); - - --- --- Name: authorized_transaction authorizedtransaction_pkey; Type: CONSTRAINT; Schema: public; Owner: root --- - -ALTER TABLE ONLY authorized_transaction - ADD CONSTRAINT authorizedtransaction_pkey PRIMARY KEY (id); - - --- --- Name: gorp_migrations gorp_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: root --- - -ALTER TABLE ONLY gorp_migrations - ADD CONSTRAINT gorp_migrations_pkey PRIMARY KEY (id); - - --- --- Name: afi_by_domain; Type: INDEX; Schema: public; Owner: root --- - -CREATE UNIQUE INDEX afi_by_domain ON allowed_fi USING btree (domain); - - --- --- Name: afi_by_public_key; Type: INDEX; Schema: public; Owner: root --- - -CREATE UNIQUE INDEX afi_by_public_key ON allowed_fi USING btree (public_key); - - --- --- Name: au_by_fi_public_key_user_id; Type: INDEX; Schema: public; Owner: root --- - -CREATE UNIQUE INDEX au_by_fi_public_key_user_id ON allowed_user USING btree (fi_public_key, user_id); - - --- --- Name: request_id; Type: INDEX; Schema: public; Owner: root --- - -CREATE UNIQUE INDEX request_id ON auth_data USING btree (request_id); - - --- --- PostgreSQL database dump complete --- - diff --git a/services/compliance/internal/db/main.go b/services/compliance/internal/db/main.go deleted file mode 100644 index 59fd58f787..0000000000 --- a/services/compliance/internal/db/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package db - -import ( - "time" - - migrate "github.com/rubenv/sql-migrate" - "github.com/stellar/go/support/db" -) - -//go:generate go-bindata -nometadata -ignore .+\.go$ -pkg db -o bindata.go ./... - -// Migrations represents all of the schema migration -var Migrations migrate.MigrationSource = &migrate.AssetMigrationSource{ - Asset: Asset, - AssetDir: AssetDir, - Dir: "migrations", -} - -type Database interface { - InsertAuthorizedTransaction(transaction *AuthorizedTransaction) error - GetAuthorizedTransactionByMemo(memo string) (*AuthorizedTransaction, error) - - InsertAllowedFI(fi *AllowedFI) error - GetAllowedFIByDomain(domain string) (*AllowedFI, error) - DeleteAllowedFIByDomain(domain string) error - - InsertAllowedUser(user *AllowedUser) error - GetAllowedUserByDomainAndUserID(domain, userID string) (*AllowedUser, error) - DeleteAllowedUserByDomainAndUserID(domain, userID string) error - - InsertAuthData(authData *AuthData) error - GetAuthData(requestID string) (*AuthData, error) -} - -type PostgresDatabase struct { - session *db.Session -} - -// AllowedFI represents allowed FI -type AllowedFI struct { - ID int64 `db:"id"` - Name string `db:"name"` - Domain string `db:"domain"` - PublicKey string `db:"public_key"` - AllowedAt time.Time `db:"allowed_at"` -} - -// AllowedUser represents allowed user -type AllowedUser struct { - ID int64 `db:"id"` - FiName string `db:"fi_name"` - FiDomain string `db:"fi_domain"` - FiPublicKey string `db:"fi_public_key"` - UserID string `db:"user_id"` - AllowedAt time.Time `db:"allowed_at"` -} - -// AuthorizedTransaction represents authorized transaction -type AuthorizedTransaction struct { - ID int64 `db:"id"` - TransactionID string `db:"transaction_id"` - Memo string `db:"memo"` - TransactionXdr string `db:"transaction_xdr"` - AuthorizedAt time.Time `db:"authorized_at"` - Data string `db:"data"` -} - -// AuthData represents auth data -type AuthData struct { - ID int64 `db:"id"` - RequestID string `db:"request_id"` - Domain string `db:"domain"` - AuthData string `db:"auth_data"` -} diff --git a/services/compliance/internal/db/migrations/01_init.sql b/services/compliance/internal/db/migrations/01_init.sql deleted file mode 100644 index ba003df257..0000000000 --- a/services/compliance/internal/db/migrations/01_init.sql +++ /dev/null @@ -1,42 +0,0 @@ --- +migrate Up -CREATE TABLE AuthorizedTransaction ( - id bigserial, - transaction_id varchar(64) NOT NULL, - memo varchar(64) NOT NULL, - transaction_xdr text NOT NULL, - authorized_at timestamp NOT NULL, - data text NOT NULL, - - PRIMARY KEY (id) -); - -CREATE TABLE AllowedFI ( - id bigserial, - name varchar(255) NOT NULL, - domain varchar(255) NOT NULL, - public_key char(56) NOT NULL, - allowed_at timestamp NOT NULL, - PRIMARY KEY (id) - -) ; - -CREATE UNIQUE INDEX afi_by_domain ON AllowedFI (domain); -CREATE UNIQUE INDEX afi_by_public_key ON AllowedFI (public_key); - -CREATE TABLE AllowedUser ( - id bigserial, - fi_name varchar(255) NOT NULL, - fi_domain varchar(255) NOT NULL, - fi_public_key char(56) NOT NULL, - user_id varchar(255) NOT NULL, - allowed_at timestamp NOT NULL, - PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX au_by_fi_public_key_user_id ON AllowedUser (fi_public_key, user_id); - - --- +migrate Down -DROP TABLE AuthorizedTransaction; -DROP TABLE AllowedFI; -DROP TABLE AllowedUser; diff --git a/services/compliance/internal/db/migrations/02_auth_data.sql b/services/compliance/internal/db/migrations/02_auth_data.sql deleted file mode 100644 index dbffdae7c4..0000000000 --- a/services/compliance/internal/db/migrations/02_auth_data.sql +++ /dev/null @@ -1,14 +0,0 @@ --- +migrate Up -CREATE TABLE AuthData ( - id bigserial, - request_id varchar(255) NOT NULL, - domain varchar(255) NOT NULL, - auth_data text NOT NULL, - - PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX request_id ON AuthData (request_id); - --- +migrate Down -DROP TABLE AuthData; diff --git a/services/compliance/internal/db/migrations/03_table_names.sql b/services/compliance/internal/db/migrations/03_table_names.sql deleted file mode 100644 index 5ab41addf2..0000000000 --- a/services/compliance/internal/db/migrations/03_table_names.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +migrate Up -ALTER TABLE AuthorizedTransaction RENAME TO authorized_transaction; -ALTER TABLE AllowedFI RENAME TO allowed_fi; -ALTER TABLE AllowedUser RENAME TO allowed_user; -ALTER TABLE AuthData RENAME TO auth_data; - --- +migrate Down -ALTER TABLE authorized_transaction RENAME TO AuthorizedTransaction; -ALTER TABLE allowed_fi RENAME TO AllowedFI; -ALTER TABLE allowed_user RENAME TO AllowedUser; -ALTER TABLE auth_data RENAME TO AuthData; \ No newline at end of file diff --git a/services/compliance/internal/db/migrations_test.go b/services/compliance/internal/db/migrations_test.go deleted file mode 100644 index 0d9d270d62..0000000000 --- a/services/compliance/internal/db/migrations_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package db - -import ( - "net/http" - "os" - "strings" - "testing" - - assetfs "github.com/elazarl/go-bindata-assetfs" - "github.com/shurcooL/httpfs/filter" - - supportHttp "github.com/stellar/go/support/http" -) - -func TestGeneratedAssets(t *testing.T) { - localAssets := filter.Keep(http.Dir("."), func(path string, fi os.FileInfo) bool { - return fi.IsDir() || strings.HasSuffix(path, ".sql") - }) - generatedAssets := &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo} - if !supportHttp.EqualFileSystems(localAssets, generatedAssets, "/") { - t.Fatalf("generated migrations does not match local migrations") - } -} diff --git a/services/compliance/internal/db/postgres.go b/services/compliance/internal/db/postgres.go deleted file mode 100644 index 4ff1a9256c..0000000000 --- a/services/compliance/internal/db/postgres.go +++ /dev/null @@ -1,170 +0,0 @@ -package db - -import ( - "database/sql" - - "github.com/stellar/go/support/db" - "github.com/stellar/go/support/errors" -) - -const ( - authorizedTransactionTableName = "authorized_transaction" - allowedFITableName = "allowed_fi" - allowedUserTableName = "allowed_user" - authDataTableName = "auth_data" -) - -func (d *PostgresDatabase) Open(dsn string) error { - var err error - d.session, err = db.Open("postgres", dsn) - if err != nil { - return err - } - - return nil -} - -func (d *PostgresDatabase) GetDB() *sql.DB { - if d.session == nil { - return nil - } - - return d.session.DB.DB -} - -func (d *PostgresDatabase) getTable(name string, session *db.Session) *db.Table { - if session == nil { - session = d.session - } - - return &db.Table{ - Name: name, - Session: session, - } -} - -// InsertAuthorizedTransaction inserts a new authorized transaction into DB. -func (d *PostgresDatabase) InsertAuthorizedTransaction(transaction *AuthorizedTransaction) error { - authorizedTransactionTable := d.getTable(authorizedTransactionTableName, nil) - _, err := authorizedTransactionTable.Insert(transaction).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting authorized trasaction") - } - - return nil -} - -// GetAuthorizedTransactionByMemo returns authorized transaction searching by memo -func (d *PostgresDatabase) GetAuthorizedTransactionByMemo(memo string) (*AuthorizedTransaction, error) { - authorizedTransactionTable := d.getTable(authorizedTransactionTableName, nil) - var authorizedTransaction AuthorizedTransaction - err := authorizedTransactionTable.Get(&authorizedTransaction, map[string]interface{}{"memo": memo}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting authorized transaction by memo") - } - } - - return &authorizedTransaction, nil -} - -// InsertAllowedFI inserts a new allowed FI into DB. -func (d *PostgresDatabase) InsertAllowedFI(fi *AllowedFI) error { - allowedFITable := d.getTable(allowedFITableName, nil) - _, err := allowedFITable.Insert(fi).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting allowed FI") - } - - return nil -} - -// GetAllowedFIByDomain returns allowed FI by a domain -func (d *PostgresDatabase) GetAllowedFIByDomain(domain string) (*AllowedFI, error) { - allowedFITable := d.getTable(allowedFITableName, nil) - var allowedFI AllowedFI - err := allowedFITable.Get(&allowedFI, map[string]interface{}{"domain": domain}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting allowed FI by domain") - } - } - - return &allowedFI, nil -} - -// DeleteAllowedFIByDomain deletes allowed FI by a domain -func (d *PostgresDatabase) DeleteAllowedFIByDomain(domain string) error { - allowedFITable := d.getTable(allowedFITableName, nil) - _, err := allowedFITable.Delete(map[string]interface{}{"domain": domain}).Exec() - return errors.Wrap(err, "Error removing allowed FI by domain") -} - -// InsertAllowedUser inserts a new allowed user into DB. -func (d *PostgresDatabase) InsertAllowedUser(user *AllowedUser) error { - allowedUserTable := d.getTable(allowedUserTableName, nil) - _, err := allowedUserTable.Insert(user).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting allowed user") - } - - return nil -} - -// GetAllowedUserByDomainAndUserID returns allowed user by domain and userID -func (d *PostgresDatabase) GetAllowedUserByDomainAndUserID(domain, userID string) (*AllowedUser, error) { - allowedUserTable := d.getTable(allowedUserTableName, nil) - var allowedUser AllowedUser - err := allowedUserTable.Get(&allowedUser, map[string]interface{}{"fi_domain": domain, "user_id": userID}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting allowed user by domain and user ID") - } - } - - return &allowedUser, nil -} - -// DeleteAllowedUserByDomainAndUserID deletes allowed user by domain and userID -func (d *PostgresDatabase) DeleteAllowedUserByDomainAndUserID(domain, userID string) error { - allowedUserTable := d.getTable(allowedUserTableName, nil) - _, err := allowedUserTable.Delete(map[string]interface{}{"fi_domain": domain, "user_id": userID}).Exec() - return errors.Wrap(err, "Error removing allowed user by domain and userID") -} - -// InsertAuthData inserts a new auth data into DB. -func (d *PostgresDatabase) InsertAuthData(authData *AuthData) error { - authDataTable := d.getTable(authDataTableName, nil) - _, err := authDataTable.Insert(authData).IgnoreCols("id").Exec() - if err != nil { - return errors.Wrap(err, "Error inserting auth data") - } - - return nil -} - -// GetAuthData gets auth data by request ID -func (d *PostgresDatabase) GetAuthData(requestID string) (*AuthData, error) { - authDataTable := d.getTable(authDataTableName, nil) - var authData AuthData - err := authDataTable.Get(&authData, map[string]interface{}{"request_id": requestID}).Exec() - if err != nil { - switch errors.Cause(err) { - case sql.ErrNoRows: - return nil, nil - default: - return nil, errors.Wrap(err, "Error getting auth data by request ID") - } - } - - return &authData, nil -} diff --git a/services/compliance/internal/handlers/main.go b/services/compliance/internal/handlers/main.go deleted file mode 100644 index fe21d3ad32..0000000000 --- a/services/compliance/internal/handlers/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package handlers - -import ( - "strconv" - "time" - - "github.com/stellar/go/clients/federation" - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/crypto" - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/support/http" -) - -// RequestHandler implements compliance server request handlers -type RequestHandler struct { - Config *config.Config `inject:""` - Client http.SimpleHTTPClientInterface `inject:""` - Database db.Database `inject:""` - SignatureSignerVerifier crypto.SignerVerifierInterface `inject:""` - StellarTomlResolver stellartoml.ClientInterface `inject:""` - FederationResolver federation.ClientInterface `inject:""` - NonceGenerator NonceGeneratorInterface `inject:""` -} - -type NonceGeneratorInterface interface { - Generate() string -} - -type NonceGenerator struct{} - -func (n *NonceGenerator) Generate() string { - return strconv.FormatInt(time.Now().UnixNano(), 10) -} - -type TestNonceGenerator struct{} - -func (n *TestNonceGenerator) Generate() string { - return "nonce" -} diff --git a/services/compliance/internal/handlers/request_handler_allow_access.go b/services/compliance/internal/handlers/request_handler_allow_access.go deleted file mode 100644 index d6d1105fad..0000000000 --- a/services/compliance/internal/handlers/request_handler_allow_access.go +++ /dev/null @@ -1,49 +0,0 @@ -package handlers - -import ( - log "github.com/sirupsen/logrus" - "net/http" - "time" - - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -// HandlerAllowAccess implements /allow_access endpoint -func (rh *RequestHandler) HandlerAllowAccess(w http.ResponseWriter, r *http.Request) { - name := r.PostFormValue("name") - domain := r.PostFormValue("domain") - publicKey := r.PostFormValue("public_key") - userID := r.PostFormValue("user_id") - - // TODO check params - - var err error - - if userID != "" { - entity := &db.AllowedUser{ - FiName: name, - FiDomain: domain, - FiPublicKey: publicKey, - UserID: userID, - AllowedAt: time.Now(), - } - err = rh.Database.InsertAllowedUser(entity) - } else { - entity := &db.AllowedFI{ - Name: name, - Domain: domain, - PublicKey: publicKey, - AllowedAt: time.Now(), - } - err = rh.Database.InsertAllowedFI(entity) - } - - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error persisting /allow entity") - helpers.Write(w, helpers.InternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} diff --git a/services/compliance/internal/handlers/request_handler_auth.go b/services/compliance/internal/handlers/request_handler_auth.go deleted file mode 100644 index fbe2797d9c..0000000000 --- a/services/compliance/internal/handlers/request_handler_auth.go +++ /dev/null @@ -1,429 +0,0 @@ -package handlers - -import ( - "bytes" - "crypto/sha256" - "encoding/base64" - "encoding/hex" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - log "github.com/sirupsen/logrus" - - baseAmount "github.com/stellar/go/amount" - "github.com/stellar/go/network" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/services/compliance/internal/db" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - httpHelpers "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/xdr" -) - -// HandlerAuth implements authorize endpoint -func (rh *RequestHandler) HandlerAuth(w http.ResponseWriter, r *http.Request) { - authreq := &compliance.AuthRequest{ - DataJSON: r.PostFormValue("data"), - Signature: r.PostFormValue("sig"), - } - - log.WithFields(log.Fields{"data": authreq.DataJSON, "sig": authreq.Signature}).Info("HandlerAuth") - - err := authreq.Validate() - if err != nil { - log.WithFields(log.Fields{"err": err}).Info(err.Error()) - httpHelpers.Write(w, httpHelpers.NewInvalidParameterError("", err.Error())) - return - } - - authData, err := authreq.Data() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error(err.Error()) - httpHelpers.Write(w, httpHelpers.InternalServerError) - 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") - errorResponse := httpHelpers.NewInvalidParameterError("data.sender", "Cannot get stellar.toml of sender") - httpHelpers.Write(w, errorResponse) - return - } - - if !shared.IsValidAccountID(senderStellarToml.SigningKey) { - errorResponse := httpHelpers.NewInvalidParameterError("data.sender", "SIGNING_KEY in stellar.toml of sender is invalid") - // TODO - // log.WithFields(errorResponse.LogData).Warn("SIGNING_KEY in stellar.toml of sender is invalid") - httpHelpers.Write(w, errorResponse) - return - } - - // Verify signature - signatureBytes, err := base64.StdEncoding.DecodeString(authreq.Signature) - if err != nil { - errorResponse := httpHelpers.NewInvalidParameterError("sig", "Invalid base64 string.") - // TODO - // log.WithFields(errorResponse.LogData).Warn("Error decoding signature") - httpHelpers.Write(w, errorResponse) - return - } - err = rh.SignatureSignerVerifier.Verify(senderStellarToml.SigningKey, []byte(authreq.DataJSON), signatureBytes) - if err != nil { - log.WithFields(log.Fields{ - "signing_key": senderStellarToml.SigningKey, - "data": authreq.Data, - "sig": authreq.Signature, - }).Warn("Invalid signature") - errorResponse := httpHelpers.NewInvalidParameterError("sig", "Invalid signature.") - httpHelpers.Write(w, errorResponse) - return - } - - b64r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(authData.Tx)) - var tx xdr.TransactionV0 - _, err = xdr.Unmarshal(b64r, &tx) - if err != nil { - errorResponse := httpHelpers.NewInvalidParameterError("data.tx", "Error decoding Transaction XDR") - log.WithFields(log.Fields{ - "err": err, - "tx": authData.Tx, - }).Warn("Error decoding Transaction XDR") - httpHelpers.Write(w, errorResponse) - return - } - - if tx.Memo.Hash == nil { - errorResponse := httpHelpers.NewInvalidParameterError("data.tx", "Transaction does not contain Memo.Hash") - log.WithFields(log.Fields{"tx": authData.Tx}).Warn("Transaction does not contain Memo.Hash") - httpHelpers.Write(w, errorResponse) - return - } - - // Validate memo preimage hash - memoPreimageHashBytes := sha256.Sum256([]byte(authData.AttachmentJSON)) - memoBytes := [32]byte(*tx.Memo.Hash) - - if memoPreimageHashBytes != memoBytes { - 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") - errorResponse := httpHelpers.NewInvalidParameterError("data.tx", "Error marshaling transaction") - httpHelpers.Write(w, errorResponse) - 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") - errorResponse := httpHelpers.NewInvalidParameterError("data.tx", "Memo preimage hash does not equal tx Memo.Hash") - httpHelpers.Write(w, errorResponse) - return - } - - attachment, err := authData.Attachment() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error getting attachment") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - transactionHash, err := network.HashTransactionV0(tx, rh.Config.NetworkPassphrase) - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error calculating tx hash") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - response := compliance.AuthResponse{} - - // Sanctions check - if rh.Config.Callbacks.Sanctions == "" { - response.TxStatus = compliance.AuthStatusOk - } else { - var senderInfo []byte - senderInfo, err = json.Marshal(attachment.Transaction.SenderInfo) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error(err.Error()) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - } - - var resp *http.Response - resp, err = rh.Client.PostForm( - rh.Config.Callbacks.Sanctions, - url.Values{"sender": {string(senderInfo)}}, - ) - if err != nil { - log.WithFields(log.Fields{ - "sanctions": rh.Config.Callbacks.Sanctions, - "err": err, - }).Error("Error sending request to sanctions server") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - defer resp.Body.Close() - var body []byte - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - log.Error("Error reading sanctions server response") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - switch resp.StatusCode { - case http.StatusOK: // AuthStatusOk - response.TxStatus = compliance.AuthStatusOk - case http.StatusAccepted: // AuthStatusPending - response.TxStatus = compliance.AuthStatusPending - - callbackResponse := callback.CallbackResponse{} - err = json.Unmarshal(body, &callbackResponse) - if err != nil { - // Set default value - response.Pending = 600 - } else { - response.Pending = callbackResponse.Pending - } - case http.StatusBadRequest: // AuthStatusError - response.TxStatus = compliance.AuthStatusError - - callbackResponse := callback.CallbackResponse{} - err = json.Unmarshal(body, &callbackResponse) - if err != nil { - log.WithFields(log.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from sanctions server") - } else { - response.Error = callbackResponse.Error - } - 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") - httpHelpers.Write(w, httpHelpers.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") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - allowedFi, err2 := rh.Database.GetAllowedFIByDomain(tokens[1]) - if err2 != nil { - log.WithFields(log.Fields{"err": err2}).Error("Error getting AllowedFi from DB") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - if allowedFi == nil { - // FI not found check AllowedUser - allowedUser, err2 := rh.Database.GetAllowedUserByDomainAndUserID(tokens[1], tokens[0]) - if err2 != nil { - log.WithFields(log.Fields{"err": err2}).Error("Error getting AllowedUser from DB") - httpHelpers.Write(w, httpHelpers.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.OperationTypePathPaymentStrictReceive { - amount = baseAmount.String(operationBody.PathPaymentStrictReceiveOp.DestAmount) - operationBody.PathPaymentStrictReceiveOp.DestAsset.Extract(&assetType, &assetCode, &assetIssuer) - } else if operationBody.Type == xdr.OperationTypePathPaymentStrictSend { - amount = baseAmount.String(operationBody.PathPaymentStrictSendOp.DestMin) - operationBody.PathPaymentStrictSendOp.DestAsset.Extract(&assetType, &assetCode, &assetIssuer) - } - } - - var senderInfo []byte - senderInfo, err = json.Marshal(attachment.Transaction.SenderInfo) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error(err.Error()) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - } - - var resp *http.Response - resp, err = rh.Client.PostForm( - rh.Config.Callbacks.AskUser, - url.Values{ - "amount": {amount}, - "asset_code": {assetCode}, - "asset_issuer": {assetIssuer}, - "sender": {string(senderInfo)}, - "note": {attachment.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") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - defer resp.Body.Close() - var body []byte - body, err = ioutil.ReadAll(resp.Body) - if err != nil { - log.Error("Error reading ask_user server response") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - switch resp.StatusCode { - case http.StatusOK: // AuthStatusOk - response.InfoStatus = compliance.AuthStatusOk - case http.StatusAccepted: // AuthStatusPending - response.InfoStatus = compliance.AuthStatusPending - - callbackResponse := callback.CallbackResponse{} - err = json.Unmarshal(body, &callbackResponse) - if err != nil { - // Set default value - response.Pending = 600 - } else { - response.Pending = callbackResponse.Pending - } - case http.StatusBadRequest: // AuthStatusError - response.InfoStatus = compliance.AuthStatusError - - callbackResponse := callback.CallbackResponse{} - err = json.Unmarshal(body, &callbackResponse) - if err != nil { - log.WithFields(log.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from sanctions server") - } else { - response.Error = callbackResponse.Error - } - 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") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - } - - if response.InfoStatus == compliance.AuthStatusOk { - // Fetch Info - fetchInfoRequest := &callback.FetchInfoRequest{Address: string(attachment.Transaction.Route)} - var resp *http.Response - resp, err = rh.Client.PostForm( - rh.Config.Callbacks.FetchInfo, - httpHelpers.ToValues(fetchInfoRequest), - ) - if err != nil { - log.WithFields(log.Fields{ - "fetch_info": rh.Config.Callbacks.FetchInfo, - "err": err, - }).Error("Error sending request to fetch_info server") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - defer resp.Body.Close() - var body []byte - 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") - httpHelpers.Write(w, httpHelpers.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") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - - response.DestInfo = string(body) - } - } else { - response.InfoStatus = compliance.AuthStatusOk - } - - if response.TxStatus == compliance.AuthStatusOk && response.InfoStatus == compliance.AuthStatusOk { - w.WriteHeader(http.StatusOK) - authorizedTransaction := &db.AuthorizedTransaction{ - TransactionID: hex.EncodeToString(transactionHash[:]), - Memo: base64.StdEncoding.EncodeToString(memoBytes[:]), - TransactionXdr: authData.Tx, - AuthorizedAt: time.Now(), - Data: authreq.DataJSON, - } - err = rh.Database.InsertAuthorizedTransaction(authorizedTransaction) - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error persisting AuthorizedTransaction") - httpHelpers.Write(w, httpHelpers.InternalServerError) - return - } - } else if response.TxStatus == compliance.AuthStatusDenied || response.InfoStatus == compliance.AuthStatusDenied { - w.WriteHeader(http.StatusForbidden) - } else if response.TxStatus == compliance.AuthStatusError || response.InfoStatus == compliance.AuthStatusError { - w.WriteHeader(http.StatusBadRequest) - } else if response.TxStatus == compliance.AuthStatusPending || response.InfoStatus == compliance.AuthStatusPending { - w.WriteHeader(http.StatusAccepted) - } - - responseBody, err := response.Marshal() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error(err.Error()) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - w.Write(responseBody) -} diff --git a/services/compliance/internal/handlers/request_handler_auth_test.go b/services/compliance/internal/handlers/request_handler_auth_test.go deleted file mode 100644 index b6bb297ab0..0000000000 --- a/services/compliance/internal/handlers/request_handler_auth_test.go +++ /dev/null @@ -1,1223 +0,0 @@ -package handlers - -import ( - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "github.com/stellar/go/network" - "github.com/stellar/go/xdr" - "net/http" - "net/url" - "strings" - "testing" - "time" - - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/support/http/httptest" - "github.com/stellar/go/txnbuild" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/services/compliance/internal/mocks" - "github.com/stellar/go/services/compliance/internal/test" -) - -func TestRequestHandlerAuthInvalidParams(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerAuth)) - defer testServer.Close() - - // auth request (no sanctions check) When data param is missing, return error - statusCode, response := mocks.GetResponse(testServer, url.Values{}) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected := test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter." - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // auth request (no sanctions check) When data is invalid, return error - params := url.Values{ - "data": {"hello world"}, - "sig": {"bad sig"}, - } - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter." - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When signature is invalid - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - attachment := compliance.Attachment{} - attachHash, err := attachment.Hash() - require.NoError(t, err) - attachmentJSON, err := attachment.Marshal() - require.NoError(t, err) - - txnOp := &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err := tx.Base64() - require.NoError(t, err) - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - require.NoError(t, err) - - authData := compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err := authData.Marshal() - require.NoError(t, err) - - params = url.Values{ - "data": {string(authDataJSON)}, - "sig": {"ACamNqa0dF8gf97URhFVKWSD7fmvZKc5At+8dCLM5ySR0HsHySF3G2WuwYP2nKjeqjKmu3U9Z3+u1P10w1KBCA=="}, - } - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(errors.New("Verify error")).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "sig" - } -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - - // When sender's stellar.toml does not contain signing key - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{}, nil).Once() - - attachHash = sha256.Sum256([]byte("{}")) - txnOp = &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err = txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err = tx.Base64() - require.NoError(t, err) - - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err = xdr.MarshalBase64(txXDR.V0.Tx) - require.NoError(t, err) - - authData = compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: "{}", - } - - authDataJSON, err = authData.Marshal() - require.NoError(t, err) - - params = url.Values{ - "data": {string(authDataJSON)}, - "sig": {"ACamNqa0dF8gf97URhFVKWSD7fmvZKc5At+8dCLM5ySR0HsHySF3G2WuwYP2nKjeqjKmu3U9Z3+u1P10w1KBCA=="}, - } - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "data.sender" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) - -} - -func TestRequestHandlerAuthValidParams(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerAuth)) - defer testServer.Close() - - // When all params are valid - attachment := compliance.Attachment{} - attachHash, err := attachment.Hash() - require.NoError(t, err) - attachHashB64 := base64.StdEncoding.EncodeToString(attachHash[:]) - attachmentJSON, err := attachment.Marshal() - require.NoError(t, err) - - txnOp := &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err := tx.Base64() - require.NoError(t, err) - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - require.NoError(t, err) - - txHashHex, err := tx.HashHex(network.TestNetworkPassphrase) - require.NoError(t, err) - - authData := compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - authDataJSON, err := authData.Marshal() - require.NoError(t, err) - - params := url.Values{ - "data": {string(authDataJSON)}, - "sig": {"ACamNqa0dF8gf97URhFVKWSD7fmvZKc5At+8dCLM5ySR0HsHySF3G2WuwYP2nKjeqjKmu3U9Z3+u1P10w1KBCA=="}, - } - - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - // It returns AuthResponse - authorizedTransaction := &db.AuthorizedTransaction{ - TransactionID: txHashHex, - Memo: attachHashB64, - TransactionXdr: txB64, - Data: params["data"][0], - } - - mockDatabase.On( - "InsertAuthorizedTransaction", - mock.AnythingOfType("*db.AuthorizedTransaction"), - ).Run(func(args mock.Arguments) { - value := args.Get(0).(*db.AuthorizedTransaction) - assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) - assert.Equal(t, authorizedTransaction.Memo, value.Memo) - assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) - assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) - assert.Equal(t, authorizedTransaction.Data, value.Data) - }).Return(nil).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected := test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "ok" -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - -} - -func TestRequestHandlerAuthSanctionsCheck(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - Sanctions: "http://sanctions", - AskUser: "http://ask_user", - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerAuth)) - defer testServer.Close() - senderInfo := compliance.SenderInfo{FirstName: "John", LastName: "Doe"} - senderInfoMap, err := senderInfo.Map() - require.NoError(t, err) - - attachment := compliance.Attachment{ - Transaction: compliance.Transaction{ - Route: "bob*acme.com", - Note: "Happy birthday", - SenderInfo: senderInfoMap, - Extra: "extra", - }, - } - - attachHash, err := attachment.Hash() - require.NoError(t, err) - attachHashB64 := base64.StdEncoding.EncodeToString(attachHash[:]) - - txnOp := &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err := tx.Base64() - require.NoError(t, err) - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - require.NoError(t, err) - - txHashHex, err := tx.HashHex(network.TestNetworkPassphrase) - require.NoError(t, err) - - attachmentJSON, err := attachment.Marshal() - require.NoError(t, err) - - senderInfoJSON, err := json.Marshal(attachment.Transaction.SenderInfo) - require.NoError(t, err) - - // When all params are valid (NeedInfo = `false`) - authData := compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err := authData.Marshal() - require.NoError(t, err) - - params := url.Values{ - "data": {string(authDataJSON)}, - "sig": {"Q2cQVOn/A+aOxrLLeUPwHmBm3LMvlfXN8tDHo4Oi6SxWWueMTDfRkC4XvRX4emLij+Npo7/GfrZ82CnT5yB5Dg=="}, - } - - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - // when sanctions server returns forbidden it returns tx_status `denied` - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{"sender": {string(senderInfoJSON)}}, - ).Return( - mocks.BuildHTTPResponse(403, "forbidden"), - nil, - ).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 403, statusCode) - expected := test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "denied" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when sanctions server returns bad request it returns tx_status `error` - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{"sender": {string(senderInfoJSON)}}, - ).Return( - mocks.BuildHTTPResponse(400, "{\"error\": \"Invalid name\"}"), - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "error", - "error": "Invalid name" -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when sanctions server returns accepted it returns tx_status `pending` - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{"sender": {string(senderInfoJSON)}}, - ).Return( - mocks.BuildHTTPResponse(202, "pending"), - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 202, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "pending", - "pending": 600 -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when sanctions server returns ok it returns tx_status `ok` and persists transaction - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{"sender": {string(senderInfoJSON)}}, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - authorizedTransaction := &db.AuthorizedTransaction{ - TransactionID: txHashHex, - Memo: attachHashB64, - TransactionXdr: txB64, - Data: params["data"][0], - } - - mockDatabase.On( - "InsertAuthorizedTransaction", - mock.AnythingOfType("*db.AuthorizedTransaction"), - ).Run(func(args mock.Arguments) { - value := args.Get(0).(*db.AuthorizedTransaction) - assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) - assert.Equal(t, authorizedTransaction.Memo, value.Memo) - assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) - assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) - assert.Equal(t, authorizedTransaction.Data, value.Data) - }).Return(nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "ok" -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) -} - -func TestRequestHandlerAuthSanctionsCheckNeedInfo(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - Sanctions: "http://sanctions", - AskUser: "http://ask_user", - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerAuth)) - defer testServer.Close() - senderInfo := compliance.SenderInfo{FirstName: "John", LastName: "Doe"} - senderInfoMap, err := senderInfo.Map() - require.NoError(t, err) - - attachment := compliance.Attachment{ - Transaction: compliance.Transaction{ - Route: "bob*acme.com", - Note: "Happy birthday", - SenderInfo: senderInfoMap, - Extra: "extra", - }, - } - - attachHash, err := attachment.Hash() - require.NoError(t, err) - attachHashB64 := base64.StdEncoding.EncodeToString(attachHash[:]) - - txnOp := &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err := tx.Base64() - require.NoError(t, err) - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - require.NoError(t, err) - - txHashHex, err := tx.HashHex(network.TestNetworkPassphrase) - require.NoError(t, err) - - attachmentJSON, err := attachment.Marshal() - require.NoError(t, err) - - senderInfoJSON, err := json.Marshal(attachment.Transaction.SenderInfo) - require.NoError(t, err) - - // When all params are valid (NeedInfo = `true`) - authData := compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: true, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err := authData.Marshal() - require.NoError(t, err) - - params := url.Values{ - "data": {string(authDataJSON)}, - "sig": {"Q2cQVOn/A+aOxrLLeUPwHmBm3LMvlfXN8tDHo4Oi6SxWWueMTDfRkC4XvRX4emLij+Npo7/GfrZ82CnT5yB5Dg=="}, - } - - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - // Make sanctions checks successful (tested in the previous test case) - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - // when ask_user server returns forbidden it returns info_status `denied` - mockHTTPClient.On( - "PostForm", - "http://ask_user", - url.Values{ - "sender": {string(senderInfoJSON)}, - "note": {attachment.Transaction.Note}, - "amount": {"20.0000000"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - }, - ).Return( - mocks.BuildHTTPResponse(403, "forbidden"), - nil, - ).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 403, statusCode) - expected := test.StringToJSONMap(`{ - "info_status": "denied", - "tx_status": "ok" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when ask_user server returns bad request it returns info_status `error` - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://ask_user", - url.Values{ - "sender": {string(senderInfoJSON)}, - "note": {attachment.Transaction.Note}, - "amount": {"20.0000000"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - }, - ).Return( - mocks.BuildHTTPResponse(400, "{\"error\": \"Invalid name\"}"), - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "error", - "tx_status": "ok", - "error": "Invalid name" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when ask_user server returns pending it returns info_status `pending` - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://ask_user", - url.Values{ - "sender": {string(senderInfoJSON)}, - "note": {attachment.Transaction.Note}, - "amount": {"20.0000000"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - }, - ).Return( - mocks.BuildHTTPResponse(202, "{\"pending\": 300}"), - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 202, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "pending", - "tx_status": "ok", - "pending": 300 - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when ask_user server returns pending but invalid response body it returns info_status `pending` (600 seconds) - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://ask_user", - url.Values{ - "sender": {string(senderInfoJSON)}, - "note": {attachment.Transaction.Note}, - "amount": {"20.0000000"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - }, - ).Return( - mocks.BuildHTTPResponse(202, "pending"), - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 202, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "pending", - "tx_status": "ok", - "pending": 600 - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when ask_user server returns ok it returns info_status `ok` and DestInfo and persists transaction - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://ask_user", - url.Values{ - "sender": {string(senderInfoJSON)}, - "note": {attachment.Transaction.Note}, - "amount": {"20.0000000"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://fetch_info", - url.Values{"address": {"bob*acme.com"}}, - ).Return( - mocks.BuildHTTPResponse(200, "user data"), - nil, - ).Once() - - authorizedTransaction := &db.AuthorizedTransaction{ - TransactionID: txHashHex, - Memo: attachHashB64, - TransactionXdr: txB64, - Data: params["data"][0], - } - - mockDatabase.On( - "InsertAuthorizedTransaction", - mock.AnythingOfType("*db.AuthorizedTransaction"), - ).Run(func(args mock.Arguments) { - value := args.Get(0).(*db.AuthorizedTransaction) - assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) - assert.Equal(t, authorizedTransaction.Memo, value.Memo) - assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) - assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) - assert.Equal(t, authorizedTransaction.Data, value.Data) - }).Return(nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "ok", - "dest_info": "user data" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When no callbacks.ask_user server - rhconfig.Callbacks.AskUser = "" - - // when FI allowed it returns info_status = `ok` and DestInfo and persists transaction - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockDatabase.On( - "GetAllowedFIByDomain", - "stellar.org", // sender = `alice*stellar.org` - ).Return( - &db.AllowedFI{}, // It just returns existing record - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://fetch_info", - url.Values{"address": {"bob*acme.com"}}, - ).Return( - mocks.BuildHTTPResponse(200, "user data"), - nil, - ).Once() - - authorizedTransaction = &db.AuthorizedTransaction{ - TransactionID: txHashHex, - Memo: attachHashB64, - TransactionXdr: txB64, - Data: params["data"][0], - } - - mockDatabase.On( - "InsertAuthorizedTransaction", - mock.AnythingOfType("*db.AuthorizedTransaction"), - ).Run(func(args mock.Arguments) { - value := args.Get(0).(*db.AuthorizedTransaction) - assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) - assert.Equal(t, authorizedTransaction.Memo, value.Memo) - assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) - assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) - assert.Equal(t, authorizedTransaction.Data, value.Data) - }).Return(nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "ok", - "dest_info": "user data" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when FI not allowed but User is allowed it returns info_status = `ok` and DestInfo and persists transaction - mockDatabase.On( - "GetAllowedFIByDomain", - "stellar.org", // sender = `alice*stellar.org` - ).Return( - nil, - nil, - ).Once() - - mockDatabase.On( - "GetAllowedUserByDomainAndUserID", - "stellar.org", // sender = `alice*stellar.org` - "alice", - ).Return( - &db.AllowedUser{}, - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - "http://fetch_info", - url.Values{"address": {"bob*acme.com"}}, - ).Return( - mocks.BuildHTTPResponse(200, "user data"), - nil, - ).Once() - - authorizedTransaction = &db.AuthorizedTransaction{ - TransactionID: txHashHex, - Memo: attachHashB64, - TransactionXdr: txB64, - Data: params["data"][0], - } - - mockDatabase.On( - "InsertAuthorizedTransaction", - mock.AnythingOfType("*db.AuthorizedTransaction"), - ).Run(func(args mock.Arguments) { - value := args.Get(0).(*db.AuthorizedTransaction) - assert.Equal(t, authorizedTransaction.TransactionID, value.TransactionID) - assert.Equal(t, authorizedTransaction.Memo, value.Memo) - assert.Equal(t, authorizedTransaction.TransactionXdr, value.TransactionXdr) - assert.WithinDuration(t, time.Now(), value.AuthorizedAt, 2*time.Second) - assert.Equal(t, authorizedTransaction.Data, value.Data) - }).Return(nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "ok", - "tx_status": "ok", - "dest_info": "user data" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when neither FI nor User is allowed it returns info_status = `denied` - mockStellartomlResolver.On( - "GetStellarTomlByAddress", - "alice*stellar.org", - ).Return(&stellartoml.Response{ - SigningKey: "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - }, nil).Once() - - mockSignerVerifier.On( - "Verify", - "GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB", - mock.AnythingOfType("[]uint8"), - mock.AnythingOfType("[]uint8"), - ).Return(nil).Once() - - mockHTTPClient.On( - "PostForm", - "http://sanctions", - url.Values{ - "sender": {string(senderInfoJSON)}, - }, - ).Return( - mocks.BuildHTTPResponse(200, "ok"), - nil, - ).Once() - - mockDatabase.On( - "GetAllowedFIByDomain", - "stellar.org", // sender = `alice*stellar.org` - ).Return( - nil, - nil, - ).Once() - - mockDatabase.On( - "GetAllowedUserByDomainAndUserID", - "stellar.org", // sender = `alice*stellar.org` - "alice", - ).Return( - nil, - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 403, statusCode) - expected = test.StringToJSONMap(`{ - "info_status": "denied", - "tx_status": "ok" -}`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - -} diff --git a/services/compliance/internal/handlers/request_handler_receive.go b/services/compliance/internal/handlers/request_handler_receive.go deleted file mode 100644 index d6297ce958..0000000000 --- a/services/compliance/internal/handlers/request_handler_receive.go +++ /dev/null @@ -1,48 +0,0 @@ -package handlers - -import ( - log "github.com/sirupsen/logrus" - "net/http" - - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" -) - -// HandlerReceive implements /receive endpoint -func (rh *RequestHandler) HandlerReceive(w http.ResponseWriter, r *http.Request) { - request := &callback.ReceiveRequest{} - err := helpers.FromRequest(r, request) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InvalidParameterError) - return - } - - err = helpers.Validate(request) - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - authorizedTransaction, err := rh.Database.GetAuthorizedTransactionByMemo(request.Memo) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error getting authorizedTransaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - if authorizedTransaction == nil { - log.WithFields(log.Fields{"memo": request.Memo}).Warn("authorizedTransaction not found") - helpers.Write(w, callback.TransactionNotFoundError) - return - } - - response := callback.ReceiveResponse{Data: authorizedTransaction.Data} - helpers.Write(w, &response) -} diff --git a/services/compliance/internal/handlers/request_handler_receive_test.go b/services/compliance/internal/handlers/request_handler_receive_test.go deleted file mode 100644 index ca8cea1794..0000000000 --- a/services/compliance/internal/handlers/request_handler_receive_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package handlers - -import ( - "net/http" - "net/url" - "strings" - "testing" - - "github.com/stellar/go/support/http/httptest" - "github.com/stretchr/testify/assert" - - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/services/compliance/internal/mocks" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" -) - -func TestRequestHandlerReceive(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerReceive)) - defer testServer.Close() - - // When memo not found, returns error - memo := "907ba78b4545338d3539683e63ecb51cf51c10adc9dabd86e92bd52339f298b9" - params := url.Values{"memo": {memo}} - - mockDatabase.On("GetAuthorizedTransactionByMemo", memo).Return(nil, nil).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 404, statusCode) - errString, err := callback.TransactionNotFoundError.Marshal() - assert.Nil(t, err) - assert.Equal(t, errString, []byte(responseString)) - - // When memo is found, return preimage - memo = "bcc649cfdb8cc557053da67df7e7fcb740dcf7f721cebe1f2082597ad0d5e7d8" - params = url.Values{"memo": {memo}} - - authorizedTransaction := db.AuthorizedTransaction{ - Memo: memo, - Data: "hello world", - } - - mockDatabase.On("GetAuthorizedTransactionByMemo", memo).Return( - &authorizedTransaction, - nil, - ).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - assert.Equal(t, "{\n \"data\": \"hello world\"\n}", responseString) - -} diff --git a/services/compliance/internal/handlers/request_handler_remove_access.go b/services/compliance/internal/handlers/request_handler_remove_access.go deleted file mode 100644 index 9c4f4c8538..0000000000 --- a/services/compliance/internal/handlers/request_handler_remove_access.go +++ /dev/null @@ -1,34 +0,0 @@ -package handlers - -import ( - log "github.com/sirupsen/logrus" - "net/http" - - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -// HandlerRemoveAccess implements /remove_access endpoint -func (rh *RequestHandler) HandlerRemoveAccess(w http.ResponseWriter, r *http.Request) { - domain := r.PostFormValue("domain") - userID := r.PostFormValue("user_id") - - // TODO check params - - if userID != "" { - err := rh.Database.DeleteAllowedUserByDomainAndUserID(domain, userID) - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error removing allowed user") - helpers.Write(w, helpers.InternalServerError) - return - } - } else { - err := rh.Database.DeleteAllowedFIByDomain(domain) - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error removing allowed FI") - helpers.Write(w, helpers.InternalServerError) - return - } - } - - w.WriteHeader(http.StatusOK) -} diff --git a/services/compliance/internal/handlers/request_handler_send.go b/services/compliance/internal/handlers/request_handler_send.go deleted file mode 100644 index 766844587e..0000000000 --- a/services/compliance/internal/handlers/request_handler_send.go +++ /dev/null @@ -1,351 +0,0 @@ -package handlers - -import ( - "encoding/json" - "io/ioutil" - "net/http" - - log "github.com/sirupsen/logrus" - "github.com/stellar/go/address" - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/protocols/federation" - "github.com/stellar/go/services/compliance/internal/db" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/bridge" - callback "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/txnbuild" -) - -// HandlerSend implements /send endpoint -func (rh *RequestHandler) HandlerSend(w http.ResponseWriter, r *http.Request) { - request := &callback.SendRequest{} - err := helpers.FromRequest(r, request) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InvalidParameterError) - return - } - - err = helpers.Validate(request) - if err != nil { - switch err := err.(type) { - case *helpers.ErrorResponse: - helpers.Write(w, err) - default: - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - } - return - } - - authDataEntity, err := rh.Database.GetAuthData(request.ID) - if err != nil { - log.Error(err.Error()) - helpers.Write(w, helpers.InternalServerError) - return - } - - if authDataEntity != nil { - var stellarToml *stellartoml.Response - stellarToml, err = rh.StellarTomlResolver.GetStellarToml(authDataEntity.Domain) - if err != nil { - log.WithFields(log.Fields{ - "destination": request.Destination, - "err": err, - }).Print("Cannot resolve address") - helpers.Write(w, callback.CannotResolveDestination) - return - } - - if stellarToml.AuthServer == "" { - log.Print("No AUTH_SERVER in stellar.toml") - helpers.Write(w, callback.AuthServerNotDefined) - return - } - - rh.sendAuthData(w, stellarToml.AuthServer, []byte(authDataEntity.AuthData)) - return - } - - var domain string - var destinationObject *federation.NameResponse - - if request.ForwardDestination == nil { - destinationObject, err = rh.FederationResolver.LookupByAddress(request.Destination) - if err != nil { - log.WithFields(log.Fields{ - "destination": request.Destination, - "err": err, - }).Print("Cannot resolve address") - helpers.Write(w, callback.CannotResolveDestination) - return - } - - _, domain, err = address.Split(request.Destination) - if err != nil { - log.WithFields(log.Fields{ - "destination": request.Destination, - "err": err, - }).Print("Cannot resolve address") - helpers.Write(w, callback.CannotResolveDestination) - return - } - } else { - destinationObject, err = rh.FederationResolver.ForwardRequest(request.ForwardDestination.Domain, request.ForwardDestination.Fields) - if err != nil { - log.WithFields(log.Fields{ - "destination": request.Destination, - "err": err, - }).Print("Cannot resolve address") - helpers.Write(w, callback.CannotResolveDestination) - return - } - - domain = request.ForwardDestination.Domain - } - - stellarToml, err := rh.StellarTomlResolver.GetStellarToml(domain) - if err != nil { - log.WithFields(log.Fields{ - "destination": request.Destination, - "err": err, - }).Print("Cannot resolve address") - helpers.Write(w, callback.CannotResolveDestination) - return - } - - if stellarToml.AuthServer == "" { - log.Print("No AUTH_SERVER in stellar.toml") - helpers.Write(w, callback.AuthServerNotDefined) - return - } - - var rSource *string - if request.Source != "" { - rSource = &request.Source - } - var operationBuilder txnbuild.Operation - - // check if Path payment - - if request.SendMax != "" { - var sendAsset protocols.Asset - if request.SendAssetCode != "" && request.SendAssetIssuer != "" { - sendAsset = protocols.Asset{Code: request.SendAssetCode, Issuer: request.SendAssetIssuer} - } else if request.SendAssetCode == "" && request.SendAssetIssuer == "" { - sendAsset = protocols.Asset{} - } else { - log.Print("Missing send asset param.") - helpers.Write(w, helpers.NewMissingParameter("send asset")) - return - } - - paymentOp := bridge.PathPaymentOperationBody{ - Source: rSource, - SendMax: request.SendMax, - SendAsset: sendAsset, - Destination: destinationObject.AccountID, - DestinationAmount: request.Amount, - DestinationAsset: protocols.Asset{Code: request.AssetCode, Issuer: request.AssetIssuer}, - Path: request.Path, - } - - operationBuilder = paymentOp.Build() - } else { - paymentOp := bridge.PaymentOperationBody{ - Source: rSource, - Destination: destinationObject.AccountID, - Amount: request.Amount, - Asset: protocols.Asset{Code: request.AssetCode, Issuer: request.AssetIssuer}, - } - - operationBuilder = paymentOp.Build() - } - - // Fetch Sender Info - senderInfo := make(map[string]string) - - if rh.Config.Callbacks.FetchInfo != "" { - fetchInfoRequest := &callback.FetchInfoRequest{Address: request.Sender} - var resp *http.Response - resp, err = rh.Client.PostForm( - rh.Config.Callbacks.FetchInfo, - helpers.ToValues(fetchInfoRequest), - ) - if err != nil { - log.WithFields(log.Fields{ - "fetch_info": rh.Config.Callbacks.FetchInfo, - "err": err, - }).Error("Error sending request to fetch_info server") - helpers.Write(w, helpers.InternalServerError) - return - } - - defer resp.Body.Close() - var body []byte - 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") - helpers.Write(w, helpers.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") - helpers.Write(w, helpers.InternalServerError) - return - } - - err = json.Unmarshal(body, &senderInfo) - if err != nil { - log.WithFields(log.Fields{ - "fetch_info": rh.Config.Callbacks.FetchInfo, - "err": err, - }).Error("Error unmarshalling sender_info server response") - helpers.Write(w, helpers.InternalServerError) - return - } - } - - attachment := &compliance.Attachment{ - Nonce: rh.NonceGenerator.Generate(), - Transaction: compliance.Transaction{ - SenderInfo: senderInfo, - Route: compliance.Route(destinationObject.Memo.Value), - Extra: request.ExtraMemo, - }, - } - - attachmentJSON, err := attachment.Marshal() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error marshalling attachment") - helpers.Write(w, helpers.InternalServerError) - return - } - attachmentHashBytes, err := attachment.Hash() - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error hashing attachment") - helpers.Write(w, helpers.InternalServerError) - return - } - - memo := txnbuild.MemoHash(attachmentHashBytes) - - transaction, err := shared.BuildTransaction( - request.Source, - rh.Config.NetworkPassphrase, - []txnbuild.Operation{operationBuilder}, - memo, - ) - if err != nil { - log.WithFields(log.Fields{"err": err}).Error("Error building transaction") - helpers.Write(w, helpers.InternalServerError) - return - } - - authData := compliance.AuthData{ - Sender: request.Sender, - NeedInfo: rh.Config.NeedsAuth, - Tx: transaction, - AttachmentJSON: string(attachmentJSON), - } - - data, err := authData.Marshal() - if err != nil { - log.Error("Error mashaling authData") - helpers.Write(w, helpers.InternalServerError) - return - } - - authDataEntity = &db.AuthData{ - RequestID: request.ID, - Domain: domain, - AuthData: string(data), - } - err = rh.Database.InsertAuthData(authDataEntity) - if err != nil { - log.WithFields(log.Fields{"err": err}).Warn("Error persisting authDataEntity") - helpers.Write(w, helpers.InternalServerError) - return - } - - rh.sendAuthData(w, stellarToml.AuthServer, data) -} - -func (rh *RequestHandler) sendAuthData(w http.ResponseWriter, authServer string, data []byte) { - var authData compliance.AuthData - err := json.Unmarshal(data, &authData) - if err != nil { - log.Error(err) - helpers.Write(w, helpers.InternalServerError) - return - } - - sig, err := rh.SignatureSignerVerifier.Sign(rh.Config.Keys.SigningSeed, data) - if err != nil { - log.Error("Error signing authData") - helpers.Write(w, helpers.InternalServerError) - return - } - - authRequest := compliance.AuthRequest{ - DataJSON: string(data), - Signature: sig, - } - resp, err := rh.Client.PostForm( - authServer, - authRequest.ToURLValues(), - ) - if err != nil { - log.WithFields(log.Fields{ - "auth_server": authServer, - "err": err, - }).Error("Error sending request to auth server") - helpers.Write(w, helpers.InternalServerError) - return - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Error("Error reading auth server response") - helpers.Write(w, helpers.InternalServerError) - return - } - - if resp.StatusCode != 200 && resp.StatusCode != 202 && resp.StatusCode != 403 { - log.WithFields(log.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error response from auth server") - helpers.Write(w, helpers.InternalServerError) - return - } - - var authResponse compliance.AuthResponse - err = json.Unmarshal(body, &authResponse) - if err != nil { - log.WithFields(log.Fields{ - "status": resp.StatusCode, - "body": string(body), - }).Error("Error unmarshalling auth response") - helpers.Write(w, helpers.InternalServerError) - return - } - - response := callback.SendResponse{ - AuthResponse: authResponse, - TransactionXdr: authData.Tx, - } - helpers.Write(w, &response) -} diff --git a/services/compliance/internal/handlers/request_handler_send_test.go b/services/compliance/internal/handlers/request_handler_send_test.go deleted file mode 100644 index 543f8063cd..0000000000 --- a/services/compliance/internal/handlers/request_handler_send_test.go +++ /dev/null @@ -1,599 +0,0 @@ -package handlers - -import ( - "github.com/stellar/go/xdr" - "net/http" - "net/url" - "strings" - "testing" - - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/protocols/federation" - "github.com/stellar/go/support/http/httptest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/services/compliance/internal/mocks" - "github.com/stellar/go/services/compliance/internal/test" - "github.com/stellar/go/txnbuild" -) - -func TestRequestHandlerSendInvalidParams(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerSend)) - defer testServer.Close() - - // When id parameter is missing, return error - params := url.Values{ - // "id": {"id"}, - "source": {"GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - "sender": {"alice*stellar.org"}, // GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD - "destination": {"bob*stellar.org"}, // GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE - "amount": {"20"}, - } - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected := test.StringToJSONMap(`{ - "code": "missing_parameter", - "message": "Required parameter is missing.", - "data": { - "name": "ID" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // when source parameter is missing - params = url.Values{ - "id": {"id"}, - // "source": {"bad"}, - "sender": {"alice*stellar.org"}, // GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD - "destination": {"bob*stellar.org"}, // GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE - "amount": {"20"}, - } - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "missing_parameter", - "message": "Required parameter is missing.", - "data": { - "name": "Source" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When source param is invalid, return error - params = url.Values{ - "id": {"id"}, - "source": {"bad"}, - "sender": {"alice*stellar.org"}, // GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD - "destination": {"bob*stellar.org"}, // GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE - "amount": {"20"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - "extra_memo": {"hello world"}, - } - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 400, statusCode) - expected = test.StringToJSONMap(`{ - "code": "invalid_parameter", - "message": "Invalid parameter.", - "data": { - "name": "Source" - } - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString, "more_info")) -} - -func TestRequestHandlerSendValidParams(t *testing.T) { - var rhconfig = &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - Callbacks: config.Callbacks{ - FetchInfo: "http://fetch_info", - }, - } - - var mockHTTPClient = new(mocks.MockHTTPClient) - var mockDatabase = new(mocks.MockDatabase) - var mockFederationResolver = new(mocks.MockFederationResolver) - var mockSignerVerifier = new(mocks.MockSignerVerifier) - var mockStellartomlResolver = new(mocks.MockStellartomlResolver) - var mockNonceGenerator = new(mocks.MockNonceGenerator) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - NonceGenerator: mockNonceGenerator, - } - - testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerSend)) - defer testServer.Close() - - // When params are valid, it returns SendResponse when success (payment) - params := url.Values{ - "id": {"id"}, - "source": {"GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - "sender": {"alice*stellar.org"}, // GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD - "destination": {"bob*stellar.org"}, // GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE - "amount": {"20"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - "extra_memo": {"hello world"}, - } - - mockDatabase.Mock.On("GetAuthData", "id").Return(nil, nil).Once() - mockDatabase.On("InsertAuthData", mock.AnythingOfType("*db.AuthData")).Run(func(args mock.Arguments) { - entity, ok := args.Get(0).(*db.AuthData) - assert.True(t, ok, "Invalid conversion") - assert.Equal(t, "id", entity.RequestID) - assert.Equal(t, "stellar.org", entity.Domain) - }).Return(nil).Once() - - senderInfo := compliance.SenderInfo{FirstName: "John", LastName: "Doe"} - senderInfoMap, err := senderInfo.Map() - require.NoError(t, err) - - authServer := "https://acme.com/auth" - - mockFederationResolver.On( - "LookupByAddress", - "bob*stellar.org", - ).Return(&federation.NameResponse{ - AccountID: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - MemoType: "text", - Memo: federation.Memo{"bob"}, - }, nil).Once() - - mockStellartomlResolver.On( - "GetStellarToml", - "stellar.org", - ).Return(&stellartoml.Response{AuthServer: authServer}, nil).Once() - - attachment := compliance.Attachment{ - Nonce: "nonce", - Transaction: compliance.Transaction{ - Route: "bob", - Note: "", - SenderInfo: senderInfoMap, - Extra: "hello world", - }, - } - - attachmentJSON, err := attachment.Marshal() - require.NoError(t, err) - attachHash, err := attachment.Hash() - require.NoError(t, err) - - txnOp := &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err := tx.Base64() - require.NoError(t, err) - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - - authData := compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err := authData.Marshal() - require.NoError(t, err) - - authRequest := compliance.AuthRequest{ - DataJSON: string(authDataJSON), - Signature: "8IGqaeusNOnoFzUYzAlW0sxXXyIOx8fxvEToUHGyTJ9bxdoXJPj6yopa3kEjkVdMIiO5fuzVluk/KTxlbROGDg==", - } - - authResponse := compliance.AuthResponse{ - InfoStatus: compliance.AuthStatusOk, - TxStatus: compliance.AuthStatusOk, - } - - authResponseJSON, err := authResponse.Marshal() - require.NoError(t, err) - - mockHTTPClient.On( - "PostForm", - rhconfig.Callbacks.FetchInfo, - url.Values{"address": {"alice*stellar.org"}}, - ).Return( - mocks.BuildHTTPResponse(200, "{\"first_name\": \"John\", \"last_name\": \"Doe\"}"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - authServer, - authRequest.ToURLValues(), - ).Return( - mocks.BuildHTTPResponse(200, string(authResponseJSON)), - nil, - ).Once() - - mockSignerVerifier.On( - "Sign", - rhconfig.Keys.SigningSeed, - []byte(authRequest.DataJSON), - ).Return(authRequest.Signature, nil).Once() - - statusCode, response := mocks.GetResponse(testServer, params) - responseString := strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected := test.StringToJSONMap(`{ - "auth_response": { - "info_status": "ok", - "tx_status": "ok" - }, - "transaction_xdr": "` + txB64 + `" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When params are valid, it returns SendResponse when success (path payment) - params["send_max"] = []string{"100"} - params["send_asset_code"] = []string{"USD"} - params["send_asset_issuer"] = []string{"GBDOSO3K4JTGSWJSIHXAOFIBMAABVM3YK3FI6VJPKIHHM56XAFIUCGD6"} - - // Native - params["path[0][asset_code]"] = []string{""} - params["path[0][asset_issuer]"] = []string{""} - // Credit - params["path[1][asset_code]"] = []string{"EUR"} - params["path[1][asset_issuer]"] = []string{"GAF3PBFQLH57KPECN4GRGHU5NUZ3XXKYYWLOTBIRJMBYHPUBWANIUCZU"} - - mockDatabase.Mock.On("GetAuthData", "id").Return(nil, nil).Once() - mockDatabase.On("InsertAuthData", mock.AnythingOfType("*db.AuthData")).Run(func(args mock.Arguments) { - entity, ok := args.Get(0).(*db.AuthData) - assert.True(t, ok, "Invalid conversion") - assert.Equal(t, "id", entity.RequestID) - assert.Equal(t, "stellar.org", entity.Domain) - }).Return(nil).Once() - - senderInfo = compliance.SenderInfo{FirstName: "John", LastName: "Doe"} - senderInfoMap, err = senderInfo.Map() - require.NoError(t, err) - - authServer = "https://acme.com/auth" - - mockFederationResolver.On( - "LookupByAddress", - "bob*stellar.org", - ).Return(&federation.NameResponse{ - AccountID: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - MemoType: "text", - Memo: federation.Memo{"bob"}, - }, nil).Once() - - mockStellartomlResolver.On( - "GetStellarToml", - "stellar.org", - ).Return(&stellartoml.Response{AuthServer: authServer}, nil).Once() - - attachment = compliance.Attachment{ - Nonce: "nonce", - Transaction: compliance.Transaction{ - Route: "bob", - Note: "", - SenderInfo: senderInfoMap, - Extra: "hello world", - }, - } - - attachmentJSON, err = attachment.Marshal() - require.NoError(t, err) - attachHash, err = attachment.Hash() - require.NoError(t, err) - - pathPaymentOp := &txnbuild.PathPayment{ - SendAsset: txnbuild.CreditAsset{Code: "USD", Issuer: "GBDOSO3K4JTGSWJSIHXAOFIBMAABVM3YK3FI6VJPKIHHM56XAFIUCGD6"}, - SendMax: "100", - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - DestAmount: "20", - DestAsset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - Path: []txnbuild.Asset{txnbuild.NativeAsset{}, txnbuild.CreditAsset{Code: "EUR", Issuer: "GAF3PBFQLH57KPECN4GRGHU5NUZ3XXKYYWLOTBIRJMBYHPUBWANIUCZU"}}, - } - - tx, err = txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{pathPaymentOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err = tx.Base64() - require.NoError(t, err) - - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err = xdr.MarshalBase64(txXDR.V0.Tx) - - authData = compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err = authData.Marshal() - require.NoError(t, err) - - authRequest = compliance.AuthRequest{ - DataJSON: string(authDataJSON), - Signature: "8IGqaeusNOnoFzUYzAlW0sxXXyIOx8fxvEToUHGyTJ9bxdoXJPj6yopa3kEjkVdMIiO5fuzVluk/KTxlbROGDg==", - } - - authResponse = compliance.AuthResponse{ - InfoStatus: compliance.AuthStatusOk, - TxStatus: compliance.AuthStatusOk, - } - - authResponseJSON, err = authResponse.Marshal() - require.NoError(t, err) - - mockHTTPClient.On( - "PostForm", - rhconfig.Callbacks.FetchInfo, - url.Values{"address": {"alice*stellar.org"}}, - ).Return( - mocks.BuildHTTPResponse(200, "{\"first_name\": \"John\", \"last_name\": \"Doe\"}"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - authServer, - authRequest.ToURLValues(), - ).Return( - mocks.BuildHTTPResponse(200, string(authResponseJSON)), - nil, - ).Once() - - mockSignerVerifier.On( - "Sign", - rhconfig.Keys.SigningSeed, - []byte(authRequest.DataJSON), - ).Return(authRequest.Signature, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "auth_response": { - "info_status": "ok", - "tx_status": "ok" - }, - "transaction_xdr": "` + txB64 + `" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - - // When params are valid, it returns SendResponse when success (Forward request) - params = url.Values{ - "id": {"id"}, - "source": {"GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - "sender": {"alice*stellar.org"}, // GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD - "destination": {"bob*stellar.org"}, // GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE - "amount": {"20"}, - "asset_code": {"USD"}, - "asset_issuer": {"GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - "extra_memo": {"hello world"}, - } - - params["forward_destination[domain]"] = []string{"stellar.org"} - params["forward_destination[fields][federation_type]"] = []string{"bank_account"} - params["forward_destination[fields][swift]"] = []string{"BOPBPHMM"} - params["forward_destination[fields][acct]"] = []string{"2382376"} - - mockDatabase.Mock.On("GetAuthData", "id").Return(nil, nil).Once() - mockDatabase.On("InsertAuthData", mock.AnythingOfType("*db.AuthData")).Run(func(args mock.Arguments) { - entity, ok := args.Get(0).(*db.AuthData) - assert.True(t, ok, "Invalid conversion") - assert.Equal(t, "id", entity.RequestID) - assert.Equal(t, "stellar.org", entity.Domain) - }).Return(nil).Once() - - senderInfo = compliance.SenderInfo{FirstName: "John", LastName: "Doe"} - senderInfoMap, err = senderInfo.Map() - require.NoError(t, err) - - authServer = "https://acme.com/auth" - - mockFederationResolver.On( - "ForwardRequest", - "stellar.org", - url.Values{ - "federation_type": []string{"bank_account"}, - "swift": []string{"BOPBPHMM"}, - "acct": []string{"2382376"}, - }, - ).Return(&federation.NameResponse{ - AccountID: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - MemoType: "text", - Memo: federation.Memo{"bob"}, - }, nil).Once() - - mockStellartomlResolver.On( - "GetStellarToml", - "stellar.org", - ).Return(&stellartoml.Response{AuthServer: authServer}, nil).Once() - - attachment = compliance.Attachment{ - Nonce: "nonce", - Transaction: compliance.Transaction{ - Route: "bob", - Note: "", - SenderInfo: senderInfoMap, - Extra: "hello world", - }, - } - - attachmentJSON, err = attachment.Marshal() - require.NoError(t, err) - attachHash, err = attachment.Hash() - require.NoError(t, err) - - txnOp = &txnbuild.Payment{ - Destination: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE", - Amount: "20", - Asset: txnbuild.CreditAsset{Code: "USD", Issuer: "GAMVF7G4GJC4A7JMFJWLUAEIBFQD5RT3DCB5DC5TJDEKQBBACQ4JZVEE"}, - SourceAccount: &txnbuild.SimpleAccount{AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD"}, - } - - tx, err = txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: "GAW77Z6GPWXSODJOMF5L5BMX6VMYGEJRKUNBC2CZ725JTQZORK74HQQD", - Sequence: 0, - }, - IncrementSequenceNum: false, - Operations: []txnbuild.Operation{txnOp}, - BaseFee: txnbuild.MinBaseFee, - Memo: txnbuild.MemoHash(attachHash), - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - require.NoError(t, err) - - txeB64, err = tx.Base64() - require.NoError(t, err) - - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - require.NoError(t, err) - txB64, err = xdr.MarshalBase64(txXDR.V0.Tx) - - authData = compliance.AuthData{ - Sender: "alice*stellar.org", - NeedInfo: false, - Tx: txB64, - AttachmentJSON: string(attachmentJSON), - } - - authDataJSON, err = authData.Marshal() - require.NoError(t, err) - - authRequest = compliance.AuthRequest{ - DataJSON: string(authDataJSON), - Signature: "8IGqaeusNOnoFzUYzAlW0sxXXyIOx8fxvEToUHGyTJ9bxdoXJPj6yopa3kEjkVdMIiO5fuzVluk/KTxlbROGDg==", - } - - authResponse = compliance.AuthResponse{ - InfoStatus: compliance.AuthStatusOk, - TxStatus: compliance.AuthStatusOk, - } - - authResponseJSON, err = authResponse.Marshal() - require.NoError(t, err) - - mockHTTPClient.On( - "PostForm", - rhconfig.Callbacks.FetchInfo, - url.Values{"address": {"alice*stellar.org"}}, - ).Return( - mocks.BuildHTTPResponse(200, "{\"first_name\": \"John\", \"last_name\": \"Doe\"}"), - nil, - ).Once() - - mockHTTPClient.On( - "PostForm", - authServer, - authRequest.ToURLValues(), - ).Return( - mocks.BuildHTTPResponse(200, string(authResponseJSON)), - nil, - ).Once() - - mockSignerVerifier.On( - "Sign", - rhconfig.Keys.SigningSeed, - []byte(authRequest.DataJSON), - ).Return(authRequest.Signature, nil).Once() - - statusCode, response = mocks.GetResponse(testServer, params) - responseString = strings.TrimSpace(string(response)) - assert.Equal(t, 200, statusCode) - expected = test.StringToJSONMap(`{ - "auth_response": { - "info_status": "ok", - "tx_status": "ok" - }, - "transaction_xdr": "` + txB64 + `" - }`) - assert.Equal(t, expected, test.StringToJSONMap(responseString)) - -} diff --git a/services/compliance/internal/handlers/request_handler_tx_status.go b/services/compliance/internal/handlers/request_handler_tx_status.go deleted file mode 100644 index 4fa52426e0..0000000000 --- a/services/compliance/internal/handlers/request_handler_tx_status.go +++ /dev/null @@ -1,83 +0,0 @@ -package handlers - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - - log "github.com/sirupsen/logrus" - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -// HandlerTxStatus implements /tx_status endpoint -func (rh *RequestHandler) HandlerTxStatus(w http.ResponseWriter, r *http.Request) { - txid := r.URL.Query().Get("id") - if txid == "" { - log.Info("unable to get query parameter") - helpers.Write(w, helpers.NewMissingParameter("id")) - return - } - response := compliance.TransactionStatusResponse{} - - if rh.Config.Callbacks.TxStatus == "" { - response.Status = compliance.TransactionStatusUnknown - } else { - - u, err := url.Parse(rh.Config.Callbacks.TxStatus) - if err != nil { - log.Error(err, "failed to parse tx status endpoint") - helpers.Write(w, helpers.InternalServerError) - return - } - - q := u.Query() - q.Set("id", txid) - u.RawQuery = q.Encode() - resp, err := rh.Client.Get(u.String()) - if err != nil { - log.WithFields(log.Fields{ - "tx_status": u.String(), - "err": err, - }).Error("Error sending request to tx_status server") - helpers.Write(w, helpers.InternalServerError) - return - } - - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Error("Error reading tx_status server response") - helpers.Write(w, helpers.InternalServerError) - return - } - - switch resp.StatusCode { - case http.StatusOK: - err := json.Unmarshal(body, &response) - if err != nil { - log.WithFields(log.Fields{ - "tx_status": rh.Config.Callbacks.TxStatus, - "body": string(body), - }).Error("Unable to decode tx_status response") - helpers.Write(w, helpers.InternalServerError) - return - } - if response.Status == "" { - response.Status = compliance.TransactionStatusUnknown - } - - default: - response.Status = compliance.TransactionStatusUnknown - } - } - - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(response) - if err != nil { - log.Error("Error encoding tx status response") - helpers.Write(w, helpers.InternalServerError) - return - } -} diff --git a/services/compliance/internal/handlers/request_handler_tx_status_test.go b/services/compliance/internal/handlers/request_handler_tx_status_test.go deleted file mode 100644 index b081dba51a..0000000000 --- a/services/compliance/internal/handlers/request_handler_tx_status_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package handlers - -import ( - "net/http" - "testing" - - "github.com/goji/httpauth" - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/mocks" - "github.com/stellar/go/support/http/httptest" -) - -func TestRequestHandlerTxStatus(t *testing.T) { - txStatusAuth := config.TxStatusAuth{ - Username: "username", - Password: "password", - } - - rhconfig := &config.Config{ - NetworkPassphrase: "Test SDF Network ; September 2015", - Keys: config.Keys{ - // GBYJZW5XFAI6XV73H5SAIUYK6XZI4CGGVBUBO3ANA2SV7KKDAXTV6AEB - SigningSeed: "SDWTLFPALQSP225BSMX7HPZ7ZEAYSUYNDLJ5QI3YGVBNRUIIELWH3XUV", - }, - TxStatusAuth: &txStatusAuth, - } - - mockHTTPClient := new(mocks.MockHTTPClient) - mockDatabase := new(mocks.MockDatabase) - mockFederationResolver := new(mocks.MockFederationResolver) - mockSignerVerifier := new(mocks.MockSignerVerifier) - mockStellartomlResolver := new(mocks.MockStellartomlResolver) - - requestHandler := RequestHandler{ - Config: rhconfig, - Client: mockHTTPClient, - Database: mockDatabase, - FederationResolver: mockFederationResolver, - SignatureSignerVerifier: mockSignerVerifier, - StellarTomlResolver: mockStellartomlResolver, - } - testServer := httptest.NewServer(t, httpauth.SimpleBasicAuth(rhconfig.TxStatusAuth.Username, - rhconfig.TxStatusAuth.Password)(http.HandlerFunc(requestHandler.HandlerTxStatus))) - // testServer := httptest.NewServer(t, http.HandlerFunc(requestHandler.HandlerTxStatus)) - defer testServer.Close() - - // It returns unauthorized when no auth - testServer.GET("/tx_status"). - WithQuery("id", "123"). - Expect(). - Status(http.StatusUnauthorized) - - // It returns unauthorized when bad auth - testServer.GET("/tx_status"). - WithBasicAuth("username", "wrong_password"). - Expect(). - Status(http.StatusUnauthorized) - - // it returns bad request when no parameter - testServer.GET("/tx_status"). - WithBasicAuth("username", "password"). - Expect(). - Status(http.StatusBadRequest) - - // it returns unknown when no tx_status endpoint in config - testServer.GET("/tx_status"). - WithBasicAuth("username", "password"). - WithQuery("id", "123"). - Expect(). - Status(http.StatusOK). - Body().Equal(`{"status":"unknown"}` + "\n") - - // it returns unknown when valid endpoint returns bad request - rhconfig.Callbacks = config.Callbacks{ - TxStatus: "http://tx_status", - } - txid := "abc123" - - mockHTTPClient.On( - "Get", - "http://tx_status?id="+txid, - ).Return( - mocks.BuildHTTPResponse(400, "badrequest"), - nil, - ).Once() - - testServer.GET("/tx_status"). - WithBasicAuth("username", "password"). - WithQuery("id", txid). - Expect(). - Status(http.StatusOK). - Body().Equal(`{"status":"unknown"}` + "\n") - - // it returns unknown when valid endpoint returns empty data - mockHTTPClient.On( - "Get", - "http://tx_status?id="+txid, - ).Return( - mocks.BuildHTTPResponse(200, "{}"), - nil, - ).Once() - - testServer.GET("/tx_status"). - WithBasicAuth("username", "password"). - WithQuery("id", txid). - Expect(). - Status(http.StatusOK). - Body().Equal(`{"status":"unknown"}` + "\n") - - // it returns response from valid endpoint with data - mockHTTPClient.On( - "Get", - "http://tx_status?id="+txid, - ).Return( - mocks.BuildHTTPResponse(200, `{"status":"delivered","msg":"cash paid"}`), - nil, - ).Once() - - testServer.GET("/tx_status"). - WithBasicAuth("username", "password"). - WithQuery("id", txid). - Expect(). - Status(http.StatusOK). - Body().Equal(`{"status":"delivered","msg":"cash paid"}` + "\n") -} diff --git a/services/compliance/internal/mocks/main.go b/services/compliance/internal/mocks/main.go deleted file mode 100644 index 3fa88d7973..0000000000 --- a/services/compliance/internal/mocks/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package mocks - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "time" - - "github.com/stellar/go/support/http/httptest" -) - -// BuildHTTPResponse is used in tests -func BuildHTTPResponse(statusCode int, body string) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Body: ioutil.NopCloser(bytes.NewBufferString(body)), - } -} - -// GetResponse is used in tests -func GetResponse(testServer *httptest.Server, values url.Values) (int, []byte) { - res, err := http.PostForm(testServer.URL, values) - if err != nil { - panic(err) - } - response, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - panic(err) - } - return res.StatusCode, response -} - -// JSONGetResponse is used in tests -func JSONGetResponse(testServer *httptest.Server, data map[string]interface{}) (int, []byte) { - j, err := json.Marshal(data) - if err != nil { - panic(err) - } - req, err := http.NewRequest("POST", testServer.URL, bytes.NewBuffer(j)) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - panic(err) - } - - response, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - panic(err) - } - return res.StatusCode, response -} - -// PredefinedTime is a time.Time object that will be returned by Now() function -var PredefinedTime time.Time - -// Now is a mocking a method -func Now() time.Time { - return PredefinedTime -} - -type MockNonceGenerator struct{} - -func (n *MockNonceGenerator) Generate() string { - return "nonce" -} diff --git a/services/compliance/internal/mocks/mock_database.go b/services/compliance/internal/mocks/mock_database.go deleted file mode 100644 index ef1c954cb0..0000000000 --- a/services/compliance/internal/mocks/mock_database.go +++ /dev/null @@ -1,83 +0,0 @@ -package mocks - -import ( - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stretchr/testify/mock" -) - -// MockDatabase ... -type MockDatabase struct { - mock.Mock -} - -// GetAuthorizedTransactionByMemo is a mocking a method -func (m *MockDatabase) GetAuthorizedTransactionByMemo(memo string) (*db.AuthorizedTransaction, error) { - a := m.Called(memo) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.AuthorizedTransaction), a.Error(1) -} - -// InsertAuthorizedTransaction is a mocking a method -func (m *MockDatabase) InsertAuthorizedTransaction(transaction *db.AuthorizedTransaction) error { - a := m.Called(transaction) - return a.Error(0) -} - -// InsertAllowedFI is a mocking a method -func (m *MockDatabase) InsertAllowedFI(fi *db.AllowedFI) error { - a := m.Called(fi) - return a.Error(0) -} - -// GetAllowedFIByDomain is a mocking a method -func (m *MockDatabase) GetAllowedFIByDomain(domain string) (*db.AllowedFI, error) { - a := m.Called(domain) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.AllowedFI), a.Error(1) -} - -// DeleteAllowedFIByDomain is a mocking a method -func (m *MockDatabase) DeleteAllowedFIByDomain(domain string) error { - a := m.Called(domain) - return a.Error(0) -} - -// InsertAllowedUser is a mocking a method -func (m *MockDatabase) InsertAllowedUser(user *db.AllowedUser) error { - a := m.Called(user) - return a.Error(0) -} - -// GetAllowedUserByDomainAndUserID is a mocking a method -func (m *MockDatabase) GetAllowedUserByDomainAndUserID(domain, userID string) (*db.AllowedUser, error) { - a := m.Called(domain, userID) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.AllowedUser), a.Error(1) -} - -// DeleteAllowedUserByDomainAndUserID is a mocking a method -func (m *MockDatabase) DeleteAllowedUserByDomainAndUserID(domain, userID string) error { - a := m.Called(domain, userID) - return a.Error(0) -} - -// InsertAuthData is a mocking a method -func (m *MockDatabase) InsertAuthData(authData *db.AuthData) error { - a := m.Called(authData) - return a.Error(0) -} - -// GetAuthData is a mocking a method -func (m *MockDatabase) GetAuthData(requestID string) (*db.AuthData, error) { - a := m.Called(requestID) - if a.Get(0) == nil { - return nil, a.Error(1) - } - return a.Get(0).(*db.AuthData), a.Error(1) -} diff --git a/services/compliance/internal/mocks/mock_federation_resolve.go b/services/compliance/internal/mocks/mock_federation_resolve.go deleted file mode 100644 index 62017c5832..0000000000 --- a/services/compliance/internal/mocks/mock_federation_resolve.go +++ /dev/null @@ -1,31 +0,0 @@ -package mocks - -import ( - "net/url" - - fprotocol "github.com/stellar/go/protocols/federation" - "github.com/stretchr/testify/mock" -) - -// MockFederationResolver ... -type MockFederationResolver struct { - mock.Mock -} - -// LookupByAddress is a mocking a method -func (m *MockFederationResolver) LookupByAddress(addy string) (*fprotocol.NameResponse, error) { - a := m.Called(addy) - return a.Get(0).(*fprotocol.NameResponse), a.Error(1) -} - -// LookupByAccountID is a mocking a method -func (m *MockFederationResolver) LookupByAccountID(aid string) (*fprotocol.IDResponse, error) { - a := m.Called(aid) - return a.Get(0).(*fprotocol.IDResponse), a.Error(1) -} - -// ForwardRequest is a mocking a method -func (m *MockFederationResolver) ForwardRequest(domain string, fields url.Values) (*fprotocol.NameResponse, error) { - a := m.Called(domain, fields) - return a.Get(0).(*fprotocol.NameResponse), a.Error(1) -} diff --git a/services/compliance/internal/mocks/mock_http_client.go b/services/compliance/internal/mocks/mock_http_client.go deleted file mode 100644 index ac3a7d9c45..0000000000 --- a/services/compliance/internal/mocks/mock_http_client.go +++ /dev/null @@ -1,37 +0,0 @@ -package mocks - -import ( - "net/http" - "net/url" - - "github.com/stretchr/testify/mock" -) - -// MockHTTPClient ... -type MockHTTPClient struct { - mock.Mock -} - -// HTTPClientInterface helps mocking http.Client in tests -type HTTPClientInterface interface { - PostForm(url string, data url.Values) (resp *http.Response, err error) - Get(url string) (resp *http.Response, err error) -} - -// PostForm is a mocking a method -func (m *MockHTTPClient) PostForm(url string, data url.Values) (resp *http.Response, err error) { - a := m.Called(url, data) - return a.Get(0).(*http.Response), a.Error(1) -} - -// Get is a mocking a method -func (m *MockHTTPClient) Get(url string) (resp *http.Response, err error) { - a := m.Called(url) - return a.Get(0).(*http.Response), a.Error(1) -} - -// Do is a mocking a method -func (m *MockHTTPClient) Do(req *http.Request) (resp *http.Response, err error) { - a := m.Called(req) - return a.Get(0).(*http.Response), a.Error(1) -} diff --git a/services/compliance/internal/mocks/mock_signer_verifier.go b/services/compliance/internal/mocks/mock_signer_verifier.go deleted file mode 100644 index 8abea36783..0000000000 --- a/services/compliance/internal/mocks/mock_signer_verifier.go +++ /dev/null @@ -1,22 +0,0 @@ -package mocks - -import ( - "github.com/stretchr/testify/mock" -) - -// MockSignerVerifier ... -type MockSignerVerifier struct { - mock.Mock -} - -// Sign is a mocking a method -func (m *MockSignerVerifier) Sign(secretSeed string, message []byte) (string, error) { - a := m.Called(secretSeed, message) - return a.String(0), a.Error(1) -} - -// Verify is a mocking a method -func (m *MockSignerVerifier) Verify(publicKey string, message, signature []byte) error { - a := m.Called(publicKey, message, signature) - return a.Error(0) -} diff --git a/services/compliance/internal/mocks/mock_stellar_toml_resolver.go b/services/compliance/internal/mocks/mock_stellar_toml_resolver.go deleted file mode 100644 index 5a4ae2338b..0000000000 --- a/services/compliance/internal/mocks/mock_stellar_toml_resolver.go +++ /dev/null @@ -1,23 +0,0 @@ -package mocks - -import ( - "github.com/stellar/go/clients/stellartoml" - "github.com/stretchr/testify/mock" -) - -// MockStellartomlResolver ... -type MockStellartomlResolver struct { - mock.Mock -} - -// GetStellarToml is a mocking a method -func (m *MockStellartomlResolver) GetStellarToml(domain string) (resp *stellartoml.Response, err error) { - a := m.Called(domain) - return a.Get(0).(*stellartoml.Response), a.Error(1) -} - -// GetStellarTomlByAddress is a mocking a method -func (m *MockStellartomlResolver) GetStellarTomlByAddress(addy string) (*stellartoml.Response, error) { - a := m.Called(addy) - return a.Get(0).(*stellartoml.Response), a.Error(1) -} diff --git a/services/compliance/internal/test/helpers.go b/services/compliance/internal/test/helpers.go deleted file mode 100644 index 37a912f3ce..0000000000 --- a/services/compliance/internal/test/helpers.go +++ /dev/null @@ -1,17 +0,0 @@ -package test - -import ( - "encoding/json" -) - -// StringToJSONMap transforms -func StringToJSONMap(value string, ignoredFields ...string) (m map[string]interface{}) { - err := json.Unmarshal([]byte(value), &m) - if err != nil { - panic(err) - } - for _, ignoredField := range ignoredFields { - delete(m, ignoredField) - } - return -} diff --git a/services/compliance/main.go b/services/compliance/main.go deleted file mode 100644 index 1cf2edc662..0000000000 --- a/services/compliance/main.go +++ /dev/null @@ -1,216 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "os" - "runtime" - "time" - - log "github.com/sirupsen/logrus" - - "github.com/facebookgo/inject" - "github.com/goji/httpauth" - "github.com/spf13/cobra" - "github.com/stellar/go/clients/federation" - "github.com/stellar/go/clients/stellartoml" - "github.com/stellar/go/services/compliance/internal/config" - "github.com/stellar/go/services/compliance/internal/crypto" - "github.com/stellar/go/services/compliance/internal/db" - "github.com/stellar/go/services/compliance/internal/handlers" - supportConfig "github.com/stellar/go/support/config" - "github.com/stellar/go/support/db/schema" - "github.com/stellar/go/support/errors" - supportHttp "github.com/stellar/go/support/http" - supportLog "github.com/stellar/go/support/log" -) - -var app *App -var rootCmd *cobra.Command -var migrateFlag bool -var configFile string -var versionFlag bool -var version = "N/A" - -func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - rootCmd.Execute() -} - -func init() { - rootCmd = &cobra.Command{ - Use: "compliance", - Short: "stellar compliance server", - Long: `stellar compliance server`, - Run: run, - } - - rootCmd.Flags().BoolVarP(&migrateFlag, "migrate-db", "", false, "migrate DB to the newest schema version") - rootCmd.Flags().StringVarP(&configFile, "config", "c", "compliance.cfg", "path to config file") - rootCmd.Flags().BoolVarP(&versionFlag, "version", "v", false, "displays compliance server version") -} - -func run(cmd *cobra.Command, args []string) { - var cfg config.Config - - err := supportConfig.Read(configFile, &cfg) - if err != nil { - switch cause := errors.Cause(err).(type) { - case *supportConfig.InvalidConfigError: - log.Error("config file: ", cause) - default: - log.Error(err) - } - os.Exit(-1) - } - - err = cfg.Validate() - if err != nil { - log.Fatal(err.Error()) - return - } - - if cfg.LogFormat == "json" { - log.SetFormatter(&log.JSONFormatter{}) - } - - app, err = NewApp(cfg, migrateFlag, versionFlag, version) - - if err != nil { - log.Fatal(err.Error()) - return - } - - app.Serve() -} - -// App is the application object -type App struct { - config config.Config - requestHandler handlers.RequestHandler -} - -// NewApp constructs an new App instance from the provided config. -func NewApp(config config.Config, migrateFlag bool, versionFlag bool, version string) (app *App, err error) { - var g inject.Graph - - var database db.PostgresDatabase - - if config.Database.URL != "" { - err = database.Open(config.Database.URL) - if err != nil { - err = fmt.Errorf("Cannot connect to a DB: %s", err) - return - } - } - - if migrateFlag { - var migrationsApplied int - migrationsApplied, err = schema.Migrate( - database.GetDB(), - db.Migrations, - schema.MigrateUp, - 0, - ) - if err != nil { - return - } - - log.Info("Applied migrations: ", migrationsApplied) - os.Exit(0) - return - } - - if versionFlag { - fmt.Printf("Compliance Server Version: %s \n", version) - os.Exit(0) - return - } - - requestHandler := handlers.RequestHandler{} - - httpClientWithTimeout := http.Client{ - Timeout: 10 * time.Second, - } - - stellartomlClient := stellartoml.Client{ - HTTP: &httpClientWithTimeout, - } - - federationClient := federation.Client{ - HTTP: &httpClientWithTimeout, - StellarTOML: &stellartomlClient, - } - - err = g.Provide( - &inject.Object{Value: &requestHandler}, - &inject.Object{Value: &config}, - &inject.Object{Value: &database}, - &inject.Object{Value: &crypto.SignerVerifier{}}, - &inject.Object{Value: &stellartomlClient}, - &inject.Object{Value: &federationClient}, - &inject.Object{Value: &httpClientWithTimeout}, - &inject.Object{Value: &handlers.NonceGenerator{}}, - ) - - if err != nil { - log.Fatal("Injector: ", err) - } - - if err := g.Populate(); err != nil { - log.Fatal("Injector: ", err) - } - - app = &App{ - config: config, - requestHandler: requestHandler, - } - return -} - -// Serve starts the server -func (a *App) Serve() { - // External endpoints - external := supportHttp.NewAPIMux(supportLog.DefaultLogger) - - // Middlewares - headers := http.Header{} - headers.Set("Content-Type", "application/json") - - external.Use(supportHttp.StripTrailingSlashMiddleware()) - external.Use(supportHttp.HeadersMiddleware(headers)) - - external.Post("/", a.requestHandler.HandlerAuth) - if a.config.TxStatusAuth != nil { - external.Method("GET", "/tx_status", httpauth.SimpleBasicAuth(a.config.TxStatusAuth.Username, a.config.TxStatusAuth.Password)(http.HandlerFunc(a.requestHandler.HandlerTxStatus))) - } - go func() { - supportHttp.Run(supportHttp.Config{ - ListenAddr: fmt.Sprintf(":%d", *a.config.ExternalPort), - Handler: external, - TLS: a.config.TLS, - OnStarting: func() { - log.Infof("External server listening on %d", *a.config.ExternalPort) - }, - }) - }() - - // Internal endpoints - internal := supportHttp.NewAPIMux(supportLog.DefaultLogger) - - internal.Use(supportHttp.StripTrailingSlashMiddleware("/admin")) - internal.Use(supportHttp.HeadersMiddleware(headers, "/admin/")) - - internal.Post("/send", a.requestHandler.HandlerSend) - internal.Post("/receive", a.requestHandler.HandlerReceive) - internal.Post("/allow_access", a.requestHandler.HandlerAllowAccess) - internal.Post("/remove_access", a.requestHandler.HandlerRemoveAccess) - - supportHttp.Run(supportHttp.Config{ - ListenAddr: fmt.Sprintf(":%d", *a.config.InternalPort), - Handler: internal, - OnStarting: func() { - log.Infof("Internal server listening on %d", *a.config.InternalPort) - }, - }) -} diff --git a/services/internal/bridge-compliance-shared/http/helpers/error_response.go b/services/internal/bridge-compliance-shared/http/helpers/error_response.go deleted file mode 100644 index 6812378ec2..0000000000 --- a/services/internal/bridge-compliance-shared/http/helpers/error_response.go +++ /dev/null @@ -1,47 +0,0 @@ -package helpers - -import ( - "encoding/json" -) - -// HTTPStatus returns ErrorResponse.Status -func (error *ErrorResponse) HTTPStatus() int { - return error.Status -} - -// Marshal marshals ErrorResponse -func (error *ErrorResponse) Marshal() ([]byte, error) { - return json.MarshalIndent(error, "", " ") -} - -// Error returns Message -func (error *ErrorResponse) Error() string { - return error.Message -} - -// NewInvalidParameterError creates and returns a new InvalidParameterError -func NewInvalidParameterError(name, moreInfo string) *ErrorResponse { - data := map[string]interface{}{} - if name != "" { - data["name"] = name - } - - return &ErrorResponse{ - Status: InvalidParameterError.Status, - Code: InvalidParameterError.Code, - Message: InvalidParameterError.Message, - MoreInfo: moreInfo, - Data: data, - } -} - -// NewMissingParameter creates and returns a new missingParameterError -func NewMissingParameter(name string) *ErrorResponse { - data := map[string]interface{}{"name": name} - return &ErrorResponse{ - Status: missingParameterError.Status, - Code: missingParameterError.Code, - Message: missingParameterError.Message, - Data: data, - } -} diff --git a/services/internal/bridge-compliance-shared/http/helpers/form.go b/services/internal/bridge-compliance-shared/http/helpers/form.go deleted file mode 100644 index ffd8ea7eb2..0000000000 --- a/services/internal/bridge-compliance-shared/http/helpers/form.go +++ /dev/null @@ -1,84 +0,0 @@ -package helpers - -import ( - "net/http" - "net/url" - "reflect" - "strconv" - - "github.com/stellar/go/support/errors" -) - -// FromRequest will populate destination fields using http.Request post values. -func FromRequest(r *http.Request, destination interface{}) error { - rvalue := reflect.ValueOf(destination).Elem() - typ := rvalue.Type() - for i := 0; i < rvalue.NumField(); i++ { - tag := typ.Field(i).Tag.Get("form") - switch tag { - case "": - case "-": - continue - default: - value := r.PostFormValue(tag) - if value == "" { - continue - } - - switch rvalue.Field(i).Kind() { - case reflect.Bool: - b, err := strconv.ParseBool(value) - if err != nil { - return err - } - rvalue.Field(i).SetBool(b) - case reflect.String: - rvalue.Field(i).SetString(value) - default: - return errors.New("Invalid value: " + value + " type for type: " + tag) - } - } - } - - s, special := destination.(SpecialValuesConvertable) - if special { - err := s.FromRequestSpecial(r, destination) - if err != nil { - return errors.Wrap(err, "Error from FromRequestSpecial") - } - } - - return nil -} - -// ToValues will create url.Values from source. -func ToValues(source interface{}) url.Values { - values := make(url.Values) - rvalue := reflect.ValueOf(source).Elem() - typ := rvalue.Type() - for i := 0; i < rvalue.NumField(); i++ { - field := rvalue.Field(i) - tag := typ.Field(i).Tag.Get("form") - if tag == "" || tag == "-" { - continue - } - switch field.Interface().(type) { - case bool: - value := rvalue.Field(i).Bool() - values.Set(tag, strconv.FormatBool(value)) - case string: - value := rvalue.Field(i).String() - if value == "" { - continue - } - values.Set(tag, value) - } - } - - s, special := source.(SpecialValuesConvertable) - if special { - s.ToValuesSpecial(values) - } - - return values -} diff --git a/services/internal/bridge-compliance-shared/http/helpers/main.go b/services/internal/bridge-compliance-shared/http/helpers/main.go deleted file mode 100644 index 697be21847..0000000000 --- a/services/internal/bridge-compliance-shared/http/helpers/main.go +++ /dev/null @@ -1,69 +0,0 @@ -package helpers - -import ( - "net/http" - "net/url" - - "github.com/stellar/go/support/errors" -) - -var ( - // InternalServerError is an error response - InternalServerError = &ErrorResponse{Code: "internal_server_error", Message: "Internal Server Error, please try again.", Status: http.StatusInternalServerError} - // InvalidParameterError is an error response - InvalidParameterError = &ErrorResponse{Code: "invalid_parameter", Message: "Invalid parameter.", Status: http.StatusBadRequest} - - // missingParameterError is an error response - missingParameterError = &ErrorResponse{Code: "missing_parameter", Message: "Required parameter is missing.", Status: http.StatusBadRequest} -) - -// SpecialValuesConvertable allows converting special values (not easily convertable): -// * from struct to url.Values -// * from http.Request to struct -type SpecialValuesConvertable interface { - FromRequestSpecial(r *http.Request, destination interface{}) error - ToValuesSpecial(values url.Values) -} - -// Response represents request. Params are additional parameters required to validate the request. -type Request interface { - Validate(params ...interface{}) error -} - -// Response represents response that can be returned by a server -type Response interface { - HTTPStatus() int - Marshal() ([]byte, error) -} - -// SuccessResponse can be embedded in success responses -type SuccessResponse struct{} - -func (r *SuccessResponse) HTTPStatus() int { - return http.StatusOK -} - -// Write writes a response to the given http.ResponseWriter -func Write(w http.ResponseWriter, response Response) error { - w.WriteHeader(response.HTTPStatus()) - body, err := response.Marshal() - if err != nil { - return errors.Wrap(err, "Error marshaling response") - } - w.Write(body) - return nil -} - -// ErrorResponse represents error response and implements server.Response and error interfaces -type ErrorResponse struct { - // HTTP status code - Status int `json:"-"` - // Error status code - Code string `json:"code"` - // Error message that will be returned to API consumer - Message string `json:"message"` - // Additional information returned to API consumer - MoreInfo string `json:"more_info,omitempty"` - // Error data that will be returned to API consumer - Data map[string]interface{} `json:"data,omitempty"` -} diff --git a/services/internal/bridge-compliance-shared/http/helpers/validate.go b/services/internal/bridge-compliance-shared/http/helpers/validate.go deleted file mode 100644 index a4134d6560..0000000000 --- a/services/internal/bridge-compliance-shared/http/helpers/validate.go +++ /dev/null @@ -1,131 +0,0 @@ -package helpers - -import ( - "strings" - - "github.com/asaskevich/govalidator" - "github.com/stellar/go/address" - "github.com/stellar/go/amount" - "github.com/stellar/go/strkey" -) - -func init() { - govalidator.CustomTypeTagMap.Set("stellar_accountid", govalidator.CustomTypeValidator(isStellarAccountID)) - govalidator.CustomTypeTagMap.Set("stellar_seed", govalidator.CustomTypeValidator(isStellarSeed)) - govalidator.CustomTypeTagMap.Set("stellar_asset_code", govalidator.CustomTypeValidator(isStellarAssetCode)) - govalidator.CustomTypeTagMap.Set("stellar_address", govalidator.CustomTypeValidator(isStellarAddress)) - govalidator.CustomTypeTagMap.Set("stellar_amount", govalidator.CustomTypeValidator(isStellarAmount)) - govalidator.CustomTypeTagMap.Set("stellar_destination", govalidator.CustomTypeValidator(isStellarDestination)) - -} - -func Validate(request Request, params ...interface{}) error { - valid, err := govalidator.ValidateStruct(request) - - if !valid { - fields := govalidator.ErrorsByField(err) - for field, errorValue := range fields { - switch { - case errorValue == "non zero value required": - return NewMissingParameter(field) - case strings.HasSuffix(errorValue, "does not validate as stellar_accountid"): - return NewInvalidParameterError(field, "Account ID must start with `G` and contain 56 alphanum characters.") - case strings.HasSuffix(errorValue, "does not validate as stellar_seed"): - return NewInvalidParameterError(field, "Account secret must start with `S` and contain 56 alphanum characters.") - case strings.HasSuffix(errorValue, "does not validate as stellar_asset_code"): - return NewInvalidParameterError(field, "Asset code must be 1-12 alphanumeric characters.") - case strings.HasSuffix(errorValue, "does not validate as stellar_address"): - return NewInvalidParameterError(field, "Stellar address must be of form user*domain.com") - case strings.HasSuffix(errorValue, "does not validate as stellar_destination"): - return NewInvalidParameterError(field, "Stellar destination must be of form user*domain.com or start with `G` and contain 56 alphanum characters.") - case strings.HasSuffix(errorValue, "does not validate as stellar_amount"): - return NewInvalidParameterError(field, "Amount must be positive and have up to 7 decimal places.") - default: - return NewInvalidParameterError(field, errorValue) - } - } - } - - return request.Validate(params...) -} - -// These are copied from support/config. Should we move them to /strkey maybe? -func isStellarAccountID(i interface{}, context interface{}) bool { - enc, ok := i.(string) - - if !ok { - return false - } - - _, err := strkey.Decode(strkey.VersionByteAccountID, enc) - return err == nil -} - -func isStellarSeed(i interface{}, context interface{}) bool { - enc, ok := i.(string) - - if !ok { - return false - } - - _, err := strkey.Decode(strkey.VersionByteSeed, enc) - return err == nil -} - -func isStellarAssetCode(i interface{}, context interface{}) bool { - code, ok := i.(string) - - if !ok { - return false - } - - if !govalidator.IsByteLength(code, 1, 12) { - return false - } - - if !govalidator.IsAlphanumeric(code) { - return false - } - - return true -} - -func isStellarAddress(i interface{}, context interface{}) bool { - addr, ok := i.(string) - - if !ok { - return false - } - - _, _, err := address.Split(addr) - return err == nil -} - -func isStellarAmount(i interface{}, context interface{}) bool { - am, ok := i.(string) - - if !ok { - return false - } - - _, err := amount.Parse(am) - return err == nil -} - -// isStellarDestination checks if `i` is either account public key or Stellar address. -func isStellarDestination(i interface{}, context interface{}) bool { - dest, ok := i.(string) - - if !ok { - return false - } - - _, err1 := strkey.Decode(strkey.VersionByteAccountID, dest) - _, _, err2 := address.Split(dest) - - if err1 != nil && err2 != nil { - return false - } - - return true -} diff --git a/services/internal/bridge-compliance-shared/main.go b/services/internal/bridge-compliance-shared/main.go deleted file mode 100644 index 040955acf2..0000000000 --- a/services/internal/bridge-compliance-shared/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package shared - -import ( - "github.com/stellar/go/keypair" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" -) - -// BuildTransaction is used in compliance server. The sequence number in built transaction will be equal 0! -func BuildTransaction(accountID, networkPassphrase string, operation []txnbuild.Operation, memo txnbuild.Memo) (string, error) { - - tx, err := txnbuild.NewTransaction( - txnbuild.TransactionParams{ - SourceAccount: &txnbuild.SimpleAccount{AccountID: accountID, Sequence: 0}, - IncrementSequenceNum: false, - Operations: operation, - BaseFee: txnbuild.MinBaseFee, - Memo: memo, - Timebounds: txnbuild.NewInfiniteTimeout(), - }, - ) - if err != nil { - return "", errors.Wrap(err, "unable to build transaction") - } - txeB64, err := tx.Base64() - if err != nil { - return "", errors.Wrap(err, "unable to serialize transaction") - } - - var txXDR xdr.TransactionEnvelope - err = xdr.SafeUnmarshalBase64(txeB64, &txXDR) - if err != nil { - return "", errors.Wrap(err, "unable to decode transaction envelope") - } - txB64, err := xdr.MarshalBase64(txXDR.V0.Tx) - if err != nil { - return "", errors.Wrap(err, "unable to encode transaction") - } - - return txB64, err -} - -// IsValidAccountID returns true if account ID is valid -func IsValidAccountID(accountID string) bool { - _, err := keypair.Parse(accountID) - if err != nil { - return false - } - - if accountID[0] != 'G' { - return false - } - - return true -} - -// IsValidSecret returns true if secret is valid -func IsValidSecret(secret string) bool { - _, err := keypair.Parse(secret) - if err != nil { - return false - } - - if secret[0] != 'S' { - return false - } - - return true -} - -// IsValidAssetCode returns true if asset code is valid -func IsValidAssetCode(code string) bool { - if len(code) < 1 || len(code) > 12 { - return false - } - return true -} diff --git a/services/internal/bridge-compliance-shared/protocols/asset.go b/services/internal/bridge-compliance-shared/protocols/asset.go deleted file mode 100644 index aeb82a98b3..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/asset.go +++ /dev/null @@ -1,41 +0,0 @@ -package protocols - -import ( - "fmt" - - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/txnbuild" -) - -// ToBaseAsset transforms Asset to github.com/stellar/go-stellar-base/build.Asset -func (a Asset) ToBaseAsset() txnbuild.Asset { - if a.Code == "" && a.Issuer == "" { - return txnbuild.NativeAsset{} - } - return txnbuild.CreditAsset{Code: a.Code, Issuer: a.Issuer} -} - -// String returns string representation of this asset -func (a Asset) String() string { - return fmt.Sprintf("Code: %s, Issuer: %s", a.Code, a.Issuer) -} - -// Validate checks if asset params are correct. -func (a Asset) Validate() error { - if a.Code != "" && a.Issuer != "" { - if !shared.IsValidAssetCode(a.Code) { - return errors.New("Invalid asset_code") - } - if !shared.IsValidAccountID(a.Issuer) { - return errors.New("Invalid asset_issuer") - } - } else if a.Code == "" && a.Issuer == "" { - // Native - return nil - } else { - return errors.New("Asset code or issuer is missing") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/authorize.go b/services/internal/bridge-compliance-shared/protocols/bridge/authorize.go deleted file mode 100644 index 4a457e445b..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/authorize.go +++ /dev/null @@ -1,44 +0,0 @@ -package bridge - -import ( - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/support/errors" -) - -// AuthorizeRequest represents request made to /authorize endpoint of bridge server -type AuthorizeRequest struct { - AccountID string `form:"account_id" valid:"required,stellar_accountid"` - AssetCode string `form:"asset_code" valid:"required,stellar_asset_code"` -} - -func (r AuthorizeRequest) Validate(params ...interface{}) error { - allowedAssets, ok := params[0].([]protocols.Asset) - if !ok { - return errors.New("Invalid assets validation param provided") - } - - issuingAccountID, ok := params[1].(string) - if !ok { - return errors.New("Invalid IssuingAccount validation param provided") - } - - if issuingAccountID == "" { - return errors.New("Issuing Account config parameter required") - } - - // Is asset allowed? - allowed := false - for _, asset := range allowedAssets { - if asset.Code == r.AssetCode && asset.Issuer == issuingAccountID { - allowed = true - break - } - } - - if !allowed { - return helpers.NewInvalidParameterError("asset", "Asset code not allowed.") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder.go deleted file mode 100644 index 838206408b..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder.go +++ /dev/null @@ -1,159 +0,0 @@ -package bridge - -import ( - "encoding/json" - "strconv" - - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// OperationType is the type of operation -type OperationType string - -const ( - // OperationTypeCreateAccount represents create_account operation - OperationTypeCreateAccount OperationType = "create_account" - // OperationTypePayment represents payment operation - OperationTypePayment OperationType = "payment" - // OperationTypePathPayment represents path_payment operation - OperationTypePathPayment OperationType = "path_payment" - // OperationTypeManageOffer represents manage_offer operation - OperationTypeManageOffer OperationType = "manage_offer" - // OperationTypeCreatePassiveOffer represents create_passive_offer operation - OperationTypeCreatePassiveOffer OperationType = "create_passive_offer" - // OperationTypeSetOptions represents set_options operation - OperationTypeSetOptions OperationType = "set_options" - // OperationTypeChangeTrust represents change_trust operation - OperationTypeChangeTrust OperationType = "change_trust" - // OperationTypeAllowTrust represents allow_trust operation - OperationTypeAllowTrust OperationType = "allow_trust" - // OperationTypeAccountMerge represents account_merge operation - OperationTypeAccountMerge OperationType = "account_merge" - // OperationTypeInflation represents inflation operation - OperationTypeInflation OperationType = "inflation" - // OperationTypeManageData represents manage_data operation - OperationTypeManageData OperationType = "manage_data" -) - -// BuilderRequest represents request made to /builder endpoint of bridge server -type BuilderRequest struct { - Source string - SequenceNumber string `json:"sequence_number"` - Operations []Operation - Signers []string -} - -// Process parses operations and creates OperationBody object for each operation -func (r BuilderRequest) Process() error { - var err error - for i, operation := range r.Operations { - var operationBody OperationBody - - switch operation.Type { - case OperationTypeCreateAccount: - var createAccount CreateAccountOperationBody - err = json.Unmarshal(operation.RawBody, &createAccount) - operationBody = createAccount - case OperationTypePayment: - var payment PaymentOperationBody - err = json.Unmarshal(operation.RawBody, &payment) - operationBody = payment - case OperationTypePathPayment: - var pathPayment PathPaymentOperationBody - err = json.Unmarshal(operation.RawBody, &pathPayment) - operationBody = pathPayment - case OperationTypeManageOffer: - var manageOffer ManageOfferOperationBody - err = json.Unmarshal(operation.RawBody, &manageOffer) - operationBody = manageOffer - case OperationTypeCreatePassiveOffer: - var manageOffer ManageOfferOperationBody - err = json.Unmarshal(operation.RawBody, &manageOffer) - manageOffer.PassiveOffer = true - operationBody = manageOffer - case OperationTypeSetOptions: - var setOptions SetOptionsOperationBody - err = json.Unmarshal(operation.RawBody, &setOptions) - operationBody = setOptions - case OperationTypeChangeTrust: - var changeTrust ChangeTrustOperationBody - err = json.Unmarshal(operation.RawBody, &changeTrust) - operationBody = changeTrust - case OperationTypeAllowTrust: - var allowTrust AllowTrustOperationBody - err = json.Unmarshal(operation.RawBody, &allowTrust) - operationBody = allowTrust - case OperationTypeAccountMerge: - var accountMerge AccountMergeOperationBody - err = json.Unmarshal(operation.RawBody, &accountMerge) - operationBody = accountMerge - case OperationTypeInflation: - var inflation InflationOperationBody - err = json.Unmarshal(operation.RawBody, &inflation) - operationBody = inflation - case OperationTypeManageData: - var manageData ManageDataOperationBody - err = json.Unmarshal(operation.RawBody, &manageData) - operationBody = manageData - default: - return helpers.NewInvalidParameterError("operations["+strconv.Itoa(i)+"][type]", "Invalid operation type.") - } - - if err != nil { - return helpers.NewInvalidParameterError("operations["+strconv.Itoa(i)+"][body]", "Operation is invalid: "+err.Error()) - } - - r.Operations[i].Body = operationBody - } - - return nil -} - -// Validate validates if the request is correct. -func (r BuilderRequest) Validate() error { - if !shared.IsValidAccountID(r.Source) { - return helpers.NewInvalidParameterError("source", "Source parameter must start with `G`.") - } - - for i, signer := range r.Signers { - if !shared.IsValidSecret(signer) { - return helpers.NewInvalidParameterError("signers["+strconv.Itoa(i)+"]", "Signer must start with `S`.") - } - } - - for _, operation := range r.Operations { - err := operation.Body.Validate() - if err != nil { - return err - } - } - - return nil -} - -// Operation struct contains operation type and body -type Operation struct { - Type OperationType - RawBody json.RawMessage `json:"body"` // Delay parsing until we know operation type - Body OperationBody `json:"-"` // Created during processing stage -} - -// OperationBody interface is a common interface for builder operations -type OperationBody interface { - // ToTransactionMutator() b.TransactionMutator - Build() txnbuild.Operation - Validate() error -} - -// BuilderResponse represents response returned by /builder endpoint of bridge server -type BuilderResponse struct { - helpers.SuccessResponse - TransactionEnvelope string `json:"transaction_envelope"` -} - -// Marshal marshals BuilderResponse -func (response *BuilderResponse) Marshal() ([]byte, error) { - return json.MarshalIndent(response, "", " ") -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_account_merge.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_account_merge.go deleted file mode 100644 index b52f73d685..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_account_merge.go +++ /dev/null @@ -1,39 +0,0 @@ -package bridge - -import ( - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// AccountMergeOperationBody represents account_merge operation -type AccountMergeOperationBody struct { - Source *string - Destination string -} - -// Build returns a txnbuild.Operation -func (op AccountMergeOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.AccountMerge{ - Destination: op.Destination, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op AccountMergeOperationBody) Validate() error { - if !shared.IsValidAccountID(op.Destination) { - return helpers.NewInvalidParameterError("destination", "Destination must start with `G`.") - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must start with `G`.") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_allow_trust.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_allow_trust.go deleted file mode 100644 index 3469b41f53..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_allow_trust.go +++ /dev/null @@ -1,57 +0,0 @@ -package bridge - -import ( - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// AllowTrustOperationBody represents allow_trust operation -type AllowTrustOperationBody struct { - Source *string - AssetCode string `json:"asset_code"` - Trustor string - Authorize bool -} - -// Build returns a txnbuild.Operation -func (op AllowTrustOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.AllowTrust{ - Trustor: op.Trustor, - Authorize: op.Authorize, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - txnOp.Type = txnbuild.CreditAsset{Code: op.AssetCode, Issuer: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op AllowTrustOperationBody) Validate() error { - // Note (Peter 23-05-2019): We need source account to be set here because it is used for - // creating an asset type which is needed by txnbuild.AllowTrust. - // to do: Update documentation for bridge server to indicate that source account is required for - // AllowTrust operation. Alternatively, update txnbuild to ignore issuer when building - // AllowTrust operations. - - if op.Source == nil { - return helpers.NewInvalidParameterError("source", "Source must be specified for AllowTrust operation.") - } - - if !shared.IsValidAssetCode(op.AssetCode) { - return helpers.NewInvalidParameterError("asset_code", "Asset code is invalid") - } - - if !shared.IsValidAccountID(op.Trustor) { - return helpers.NewInvalidParameterError("trustor", "Trustor must be a public key (starting with `G`).") - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_change_trust.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_change_trust.go deleted file mode 100644 index a4772f1454..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_change_trust.go +++ /dev/null @@ -1,55 +0,0 @@ -package bridge - -import ( - "github.com/stellar/go/amount" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/txnbuild" -) - -// ChangeTrustOperationBody represents change_trust operation -type ChangeTrustOperationBody struct { - Source *string - Asset protocols.Asset - // nil means max limit - Limit *string -} - -// Build returns a txnbuild.Operation -func (op ChangeTrustOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.ChangeTrust{ - Line: txnbuild.CreditAsset{Code: op.Asset.Code, Issuer: op.Asset.Issuer}, - } - - if op.Limit != nil { - txnOp.Limit = *op.Limit - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op ChangeTrustOperationBody) Validate() error { - err := op.Asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", err.Error()) - } - - if op.Limit != nil { - _, err := amount.Parse(*op.Limit) - if err != nil { - return helpers.NewInvalidParameterError("limit", "Limit is not a valid amount.") - } - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_create_account.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_create_account.go deleted file mode 100644 index d97346628f..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_create_account.go +++ /dev/null @@ -1,47 +0,0 @@ -package bridge - -import ( - "github.com/stellar/go/amount" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// CreateAccountOperationBody represents create_account operation -type CreateAccountOperationBody struct { - Source *string - Destination string - StartingBalance string `json:"starting_balance"` -} - -// Build returns a txnbuild.Operation -func (op CreateAccountOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.CreateAccount{ - Destination: op.Destination, - Amount: op.StartingBalance, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op CreateAccountOperationBody) Validate() error { - if !shared.IsValidAccountID(op.Destination) { - return helpers.NewInvalidParameterError("destination", "Destination must be a public key (starting with `G`)") - } - - _, err := amount.Parse(op.StartingBalance) - if err != nil { - return helpers.NewInvalidParameterError("starting_balance", "Not a valid amount.") - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`)") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_inflation.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_inflation.go deleted file mode 100644 index 9d4d1f1cb2..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_inflation.go +++ /dev/null @@ -1,44 +0,0 @@ -package bridge - -import ( - b "github.com/stellar/go/build" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// InflationOperationBody represents inflation operation -type InflationOperationBody struct { - Source *string -} - -// Build returns a txnbuild.Operation -func (op InflationOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.Inflation{} - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// ToTransactionMutator returns go-stellar-base TransactionMutator -func (op InflationOperationBody) ToTransactionMutator() b.TransactionMutator { - var mutators []interface{} - - if op.Source != nil { - mutators = append(mutators, b.SourceAccount{*op.Source}) - } - - return b.Inflation(mutators...) -} - -// Validate validates if operation body is valid. -func (op InflationOperationBody) Validate() error { - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_data.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_data.go deleted file mode 100644 index 6811fde554..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_data.go +++ /dev/null @@ -1,52 +0,0 @@ -package bridge - -import ( - "encoding/base64" - - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// ManageDataOperationBody represents manage_data operation -type ManageDataOperationBody struct { - Source *string - Name string - Data string -} - -// Build returns a txnbuild.Operation -func (op ManageDataOperationBody) Build() txnbuild.Operation { - - // This is validated in Validate() - data, _ := base64.StdEncoding.DecodeString(op.Data) - - txnOp := txnbuild.ManageData{ - Name: op.Name, - Value: data, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op ManageDataOperationBody) Validate() error { - if len(op.Name) > 64 { - return helpers.NewInvalidParameterError("name", "Name must be less than or equal 64 characters.") - } - - data, err := base64.StdEncoding.DecodeString(op.Data) - if err != nil || len(data) > 64 { - return helpers.NewInvalidParameterError("data", "Not a valid base64 string.") - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_offer.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_offer.go deleted file mode 100644 index a97245714e..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_manage_offer.go +++ /dev/null @@ -1,75 +0,0 @@ -package bridge - -import ( - "strconv" - - "github.com/stellar/go/txnbuild" - - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" -) - -// ManageOfferOperationBody represents manage_offer operation -type ManageOfferOperationBody struct { - PassiveOffer bool `json:"-"` - Source *string - Selling protocols.Asset - Buying protocols.Asset - Amount string - Price string - OfferID *string `json:"offer_id"` -} - -// Build returns a txnbuild.Operation -func (op ManageOfferOperationBody) Build() txnbuild.Operation { - if op.PassiveOffer { - txnOp := txnbuild.CreatePassiveSellOffer{ - Selling: op.Selling.ToBaseAsset(), - Buying: op.Buying.ToBaseAsset(), - Amount: op.Amount, - Price: op.Price, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp - } - - txnOp := txnbuild.ManageSellOffer{ - Selling: txnbuild.CreditAsset{Code: op.Selling.Code, Issuer: op.Selling.Issuer}, - Buying: txnbuild.CreditAsset{Code: op.Buying.Code, Issuer: op.Buying.Issuer}, - Amount: op.Amount, - Price: op.Price, - } - - if op.OfferID != nil { - // Validated in Validate() - u, _ := strconv.ParseInt(*op.OfferID, 10, 64) - txnOp.OfferID = u - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op ManageOfferOperationBody) Validate() error { - if op.OfferID != nil { - _, err := strconv.ParseInt(*op.OfferID, 10, 64) - if err != nil { - return helpers.NewInvalidParameterError("offer_id", "Not a number.") - } - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_path_payment.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_path_payment.go deleted file mode 100644 index 2808c4efbd..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_path_payment.go +++ /dev/null @@ -1,127 +0,0 @@ -package bridge - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/stellar/go/amount" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/txnbuild" -) - -// PathPaymentOperationBody represents path_payment operation -type PathPaymentOperationBody struct { - Source *string - - SendMax string `json:"send_max"` - SendAsset protocols.Asset `json:"send_asset"` - - Destination string - DestinationAmount string `json:"destination_amount"` - DestinationAsset protocols.Asset `json:"destination_asset"` - - Path []protocols.Asset -} - -// ToValuesSpecial converts special values from http.Request to struct -func (op *PathPaymentOperationBody) FromRequestSpecial(r *http.Request, destination interface{}) error { - var path []protocols.Asset - - for i := 0; i < 5; i++ { - codeFieldName := fmt.Sprintf(protocols.PathCodeField, i) - issuerFieldName := fmt.Sprintf(protocols.PathIssuerField, 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 == "" { - path = append(path, protocols.Asset{}) - } else { - path = append(path, protocols.Asset{code, issuer}) - } - } - - op.Path = path - return nil -} - -// ToValuesSpecial adds special values (not easily convertable) to given url.Values -func (op PathPaymentOperationBody) ToValuesSpecial(values url.Values) { - for i, asset := range op.Path { - values.Set(fmt.Sprintf(protocols.PathCodeField, i), asset.Code) - values.Set(fmt.Sprintf(protocols.PathIssuerField, i), asset.Issuer) - } -} - -// Build returns a txnbuild.Operation -func (op PathPaymentOperationBody) Build() txnbuild.Operation { - - var paths []txnbuild.Asset - - for _, asset := range op.Path { - paths = append(paths, asset.ToBaseAsset()) - } - - txnOp := txnbuild.PathPayment{ - Destination: op.Destination, - DestAmount: op.DestinationAmount, - DestAsset: op.DestinationAsset.ToBaseAsset(), - SendAsset: op.SendAsset.ToBaseAsset(), - SendMax: op.SendMax, - Path: paths, - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op PathPaymentOperationBody) Validate() error { - if !shared.IsValidAccountID(op.Destination) { - return helpers.NewInvalidParameterError("destination", "Destination must be a public key (starting with `G`).") - } - - _, err := amount.Parse(op.SendMax) - if err != nil { - return helpers.NewInvalidParameterError("send_max", "Not a valid amount.") - } - - _, err = amount.Parse(op.DestinationAmount) - if err != nil { - return helpers.NewInvalidParameterError("destination_amount", "Not a valid amount.") - } - - err = op.SendAsset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("send_asset", err.Error()) - } - - err = op.DestinationAsset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("destination_asset", err.Error()) - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - for i, asset := range op.Path { - err := asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError(fmt.Sprintf("path[%d]", i), err.Error()) - } - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_payment.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_payment.go deleted file mode 100644 index a3d4a52b70..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_payment.go +++ /dev/null @@ -1,55 +0,0 @@ -package bridge - -import ( - "github.com/stellar/go/amount" - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - "github.com/stellar/go/txnbuild" -) - -// PaymentOperationBody represents payment operation -type PaymentOperationBody struct { - Source *string - Destination string - Amount string - Asset protocols.Asset -} - -// Build returns a txnbuild.Operation -func (op PaymentOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.Payment{ - Destination: op.Destination, - Amount: op.Amount, - Asset: op.Asset.ToBaseAsset(), - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op PaymentOperationBody) Validate() error { - if !shared.IsValidAccountID(op.Destination) { - return helpers.NewInvalidParameterError("destination", "Destination must be a public key (starting with `G`).") - } - - _, err := amount.Parse(op.Amount) - if err != nil { - return helpers.NewInvalidParameterError("amount", "Invalid amount.") - } - - err = op.Asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", "Invalid asset.") - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/builder_set_options.go b/services/internal/bridge-compliance-shared/protocols/bridge/builder_set_options.go deleted file mode 100644 index b429692888..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/builder_set_options.go +++ /dev/null @@ -1,100 +0,0 @@ -package bridge - -import ( - shared "github.com/stellar/go/services/internal/bridge-compliance-shared" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/txnbuild" -) - -// SetOptionsOperationBody represents set_options operation -type SetOptionsOperationBody struct { - Source *string - InflationDest *string `json:"inflation_dest"` - SetFlags *[]int `json:"set_flags"` - ClearFlags *[]int `json:"clear_flags"` - MasterWeight *uint32 `json:"master_weight"` - LowThreshold *uint32 `json:"low_threshold"` - MediumThreshold *uint32 `json:"medium_threshold"` - HighThreshold *uint32 `json:"high_threshold"` - HomeDomain *string `json:"home_domain"` - Signer *SetOptionsSigner `json:"signer"` -} - -// SetOptionsSigner is a struct that representing signer in SetOptions operation body -type SetOptionsSigner struct { - PublicKey string `json:"public_key"` - Weight uint32 `json:"weight"` -} - -// Build returns a txnbuild.Operation -func (op SetOptionsOperationBody) Build() txnbuild.Operation { - txnOp := txnbuild.SetOptions{} - - if op.InflationDest != nil { - txnOp.InflationDestination = op.InflationDest - } - - if op.SetFlags != nil { - for _, flag := range *op.SetFlags { - txnOp.SetFlags = append(txnOp.SetFlags, txnbuild.AccountFlag(flag)) - } - } - - if op.ClearFlags != nil { - for _, flag := range *op.ClearFlags { - txnOp.ClearFlags = append(txnOp.ClearFlags, txnbuild.AccountFlag(flag)) - } - } - - if op.MasterWeight != nil { - txnOp.MasterWeight = txnbuild.NewThreshold(txnbuild.Threshold(*op.MasterWeight)) - } - - if op.LowThreshold != nil { - txnOp.LowThreshold = txnbuild.NewThreshold(txnbuild.Threshold(*op.LowThreshold)) - } - - if op.MediumThreshold != nil { - txnOp.MediumThreshold = txnbuild.NewThreshold(txnbuild.Threshold(*op.MediumThreshold)) - } - - if op.HighThreshold != nil { - txnOp.HighThreshold = txnbuild.NewThreshold(txnbuild.Threshold(*op.HighThreshold)) - } - - if op.HomeDomain != nil { - txnOp.HomeDomain = op.HomeDomain - } - - if op.Signer != nil { - txnOp.Signer = &txnbuild.Signer{ - Address: op.Signer.PublicKey, - Weight: txnbuild.Threshold(op.Signer.Weight), - } - } - - if op.Source != nil { - txnOp.SourceAccount = &txnbuild.SimpleAccount{AccountID: *op.Source} - } - - return &txnOp -} - -// Validate validates if operation body is valid. -func (op SetOptionsOperationBody) Validate() error { - if op.InflationDest != nil && !shared.IsValidAccountID(*op.InflationDest) { - return helpers.NewInvalidParameterError("inflation_dest", "Inflation destination must be a public key (starting with `G`).") - } - - if op.Signer != nil { - if !shared.IsValidAccountID(op.Signer.PublicKey) { - return helpers.NewInvalidParameterError("signer.public_key", "Public key invlaid, must start with `G`.") - } - } - - if op.Source != nil && !shared.IsValidAccountID(*op.Source) { - return helpers.NewInvalidParameterError("source", "Source must be a public key (starting with `G`).") - } - - return nil -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/payment.go b/services/internal/bridge-compliance-shared/protocols/bridge/payment.go deleted file mode 100644 index 5d326b87bf..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/payment.go +++ /dev/null @@ -1,220 +0,0 @@ -package bridge - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/stellar/go/keypair" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" - complianceServer "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols/compliance" - "github.com/stellar/go/support/errors" -) - -var ( - // input errors - - // PaymentCannotResolveDestination is an error response - PaymentCannotResolveDestination = &helpers.ErrorResponse{Code: "cannot_resolve_destination", Message: "Cannot resolve federated Stellar address.", Status: http.StatusBadRequest} - // PaymentCannotUseMemo is an error response - PaymentCannotUseMemo = &helpers.ErrorResponse{Code: "cannot_use_memo", Message: "Memo given in request but federation returned memo fields.", Status: http.StatusBadRequest} - // PaymentSourceNotExist is an error response - PaymentSourceNotExist = &helpers.ErrorResponse{Code: "source_not_exist", Message: "Source account does not exist.", Status: http.StatusBadRequest} - // PaymentAssetCodeNotAllowed is an error response - PaymentAssetCodeNotAllowed = &helpers.ErrorResponse{Code: "asset_code_not_allowed", Message: "Given asset_code not allowed.", Status: http.StatusBadRequest} - - // compliance - - // PaymentPending is an error response - PaymentPending = &helpers.ErrorResponse{Code: "pending", Message: "Transaction pending. Repeat your request after given time.", Status: http.StatusAccepted} - // PaymentDenied is an error response - PaymentDenied = &helpers.ErrorResponse{Code: "denied", Message: "Transaction denied by destination.", Status: http.StatusForbidden} -) - -// PaymentRequest represents request made to /payment endpoint of the bridge server -type PaymentRequest struct { - // Payment ID - ID string `form:"id" valid:"optional"` - // Source account secret - Source string `form:"source" valid:"optional,stellar_seed"` - // Sender address (like alice*stellar.org) - Sender string `form:"sender" valid:"optional,stellar_address"` - // Destination address (like bob*stellar.org) - Destination string `form:"destination" valid:"optional,stellar_destination"` - // ForwardDestination - ForwardDestination *protocols.ForwardDestination `form:"forward_destination" valid:"-"` - // Memo type - MemoType string `form:"memo_type" valid:"optional"` - // Memo value - Memo string `form:"memo" valid:"optional"` - // Amount destination should receive - Amount string `form:"amount" valid:"required,stellar_amount"` - // Code of the asset destination should receive - AssetCode string `form:"asset_code" valid:"optional,stellar_asset_code"` - // Issuer of the asset destination should receive - AssetIssuer string `form:"asset_issuer" valid:"optional,stellar_accountid"` - // Only for path_payment - SendMax string `form:"send_max" valid:"optional,stellar_amount"` - // Only for path_payment - SendAssetCode string `form:"send_asset_code" valid:"optional,stellar_asset_code"` - // Only for path_payment - SendAssetIssuer string `form:"send_asset_issuer" valid:"optional,stellar_accountid"` - // path[n][asset_code] path[n][asset_issuer] - Path []protocols.Asset `form:"path" valid:"optional"` - // Determined whether to use compliance protocol or to send a simple payment. - UseCompliance bool `form:"use_compliance" valid:"-"` - // Extra memo. If set, UseCompliance value will be ignored and it will use compliance. - ExtraMemo string `form:"extra_memo" valid:"-"` -} - -// ToValuesSpecial converts special values from http.Request to struct -func (request *PaymentRequest) FromRequestSpecial(r *http.Request, destination interface{}) error { - var forwardDestination protocols.ForwardDestination - forwardDestination.Domain = r.PostFormValue("forward_destination[domain]") - forwardDestination.Fields = make(url.Values) - - err := r.ParseForm() - if err != nil { - return err - } - - for key := range r.PostForm { - matches := protocols.FederationDestinationFieldName.FindStringSubmatch(key) - if len(matches) < 2 { - continue - } - - fieldName := matches[1] - forwardDestination.Fields.Add(fieldName, r.PostFormValue(key)) - } - - if forwardDestination.Domain != "" && len(forwardDestination.Fields) > 0 { - request.ForwardDestination = &forwardDestination - } - - var path []protocols.Asset - - for i := 0; i < 5; i++ { - codeFieldName := fmt.Sprintf(protocols.PathCodeField, i) - issuerFieldName := fmt.Sprintf(protocols.PathIssuerField, 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 == "" { - path = append(path, protocols.Asset{}) - } else { - path = append(path, protocols.Asset{code, issuer}) - } - } - - request.Path = path - - return nil -} - -// ToValuesSpecial adds special values (not easily convertable) to given url.Values -func (request PaymentRequest) ToValuesSpecial(values url.Values) { - if request.ForwardDestination != nil { - values.Add("forward_destination[domain]", request.ForwardDestination.Domain) - for key := range request.ForwardDestination.Fields { - values.Add(fmt.Sprintf("forward_destination[fields][%s]", key), request.ForwardDestination.Fields.Get(key)) - } - } - - for i, asset := range request.Path { - values.Set(fmt.Sprintf(protocols.PathCodeField, i), asset.Code) - values.Set(fmt.Sprintf(protocols.PathIssuerField, i), asset.Issuer) - } -} - -// ToComplianceSendRequest transforms PaymentRequest to complianceServer.SendRequest -func (request *PaymentRequest) ToComplianceSendRequest() *complianceServer.SendRequest { - sourceKeypair, _ := keypair.Parse(request.Source) - return &complianceServer.SendRequest{ - ID: request.ID, - // Compliance does not sign the transaction, it just needs a public key - Source: sourceKeypair.Address(), - Sender: request.Sender, - Destination: request.Destination, - ForwardDestination: request.ForwardDestination, - Amount: request.Amount, - AssetCode: request.AssetCode, - AssetIssuer: request.AssetIssuer, - SendMax: request.SendMax, - SendAssetCode: request.SendAssetCode, - SendAssetIssuer: request.SendAssetIssuer, - Path: request.Path, - ExtraMemo: request.ExtraMemo, - } -} - -// Validate is additional validation method to validate special fields. -func (request *PaymentRequest) Validate(params ...interface{}) error { - baseSeed, ok := params[0].(string) - if !ok { - return errors.New("Invalid `baseSeed` validation param provided") - } - - // If baseSeed is empty then request.Source is required - if baseSeed == "" && request.Source == "" { - return helpers.NewMissingParameter("source") - } - - if request.Destination == "" && request.ForwardDestination == nil { - return helpers.NewMissingParameter("destination") - } - - asset := protocols.Asset{request.AssetCode, request.AssetIssuer} - err := asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", err.Error()) - } - - sendAsset := protocols.Asset{request.SendAssetCode, request.SendAssetIssuer} - err = sendAsset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", err.Error()) - } - - for i, asset := range request.Path { - err := asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError(fmt.Sprintf("path[%d]", i), err.Error()) - } - } - - return nil -} - -// NewPaymentPendingError creates a new PaymentPending error -func NewPaymentPendingError(seconds int) *helpers.ErrorResponse { - return &helpers.ErrorResponse{ - Status: PaymentPending.Status, - Code: PaymentPending.Code, - Message: PaymentPending.Message, - Data: map[string]interface{}{"pending": seconds}, - } -} - -// PaymentResponse represents a response from the bridge server when a payment is received. -type PaymentResponse struct { - ID string - Type string - PagingToken string - From string - To string - AssetType string - AssetCode string - AssetIssuer string - Amount string - TransactionHash string - MemoType string - Memo string -} diff --git a/services/internal/bridge-compliance-shared/protocols/bridge/reprocess.go b/services/internal/bridge-compliance-shared/protocols/bridge/reprocess.go deleted file mode 100644 index 8d96517b4e..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/bridge/reprocess.go +++ /dev/null @@ -1,36 +0,0 @@ -package bridge - -import ( - "encoding/json" - "net/http" -) - -// ReprocessRequest represents request made to /reprocess endpoint of bridge server -type ReprocessRequest struct { - OperationID string `form:"operation_id" valid:"required"` - // Force is required for reprocessing successful payments. Please use with caution! - Force bool `form:"force" valid:"-"` -} - -func (r ReprocessRequest) Validate(params ...interface{}) error { - // No custom validations - return nil -} - -// ReprocessResponse represents a response returned by /reprocess endpoint -type ReprocessResponse struct { - Status string `json:"status"` - Message string `json:"message,omitempty"` -} - -func (r ReprocessResponse) HTTPStatus() int { - if r.Status == "ok" { - return http.StatusOK - } else { - return http.StatusBadRequest - } -} - -func (r ReprocessResponse) Marshal() ([]byte, error) { - return json.MarshalIndent(r, "", " ") -} diff --git a/services/internal/bridge-compliance-shared/protocols/compliance/callback.go b/services/internal/bridge-compliance-shared/protocols/compliance/callback.go deleted file mode 100644 index 85e1da0f0d..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/compliance/callback.go +++ /dev/null @@ -1,8 +0,0 @@ -package compliance - -// CallbackResponse is a response from Sanctions and AskUser callbacks when they return 202 Accepted or 400 Bad Requests statuses -type CallbackResponse struct { - // Estimated number of seconds utill the sender can check back for a change in status. - Pending int `json:"pending"` - Error string `json:"error"` -} diff --git a/services/internal/bridge-compliance-shared/protocols/compliance/errors.go b/services/internal/bridge-compliance-shared/protocols/compliance/errors.go deleted file mode 100644 index f73e426220..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/compliance/errors.go +++ /dev/null @@ -1,21 +0,0 @@ -package compliance - -import ( - "net/http" - - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -var ( - // /receive - - // TransactionNotFoundError is an error response - TransactionNotFoundError = &helpers.ErrorResponse{Code: "transaction_not_found", Message: "Transaction not found.", Status: http.StatusNotFound} - - // /send - - // CannotResolveDestination is an error response - CannotResolveDestination = &helpers.ErrorResponse{Code: "cannot_resolve_destination", Message: "Cannot resolve federated Stellar address.", Status: http.StatusBadRequest} - // AuthServerNotDefined is an error response - AuthServerNotDefined = &helpers.ErrorResponse{Code: "auth_server_not_defined", Message: "No AUTH_SERVER defined in stellar.toml file.", Status: http.StatusBadRequest} -) diff --git a/services/internal/bridge-compliance-shared/protocols/compliance/fetch_info.go b/services/internal/bridge-compliance-shared/protocols/compliance/fetch_info.go deleted file mode 100644 index 28a2b1545f..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/compliance/fetch_info.go +++ /dev/null @@ -1,13 +0,0 @@ -package compliance - -// FetchInfoRequest represents a request sent to fetch_info callback -type FetchInfoRequest struct { - Address string `form:"address"` -} - -// FetchInfoResponse represents a response returned by fetch_info callback -type FetchInfoResponse struct { - Name string `json:"name"` - Address string `json:"address"` - DateOfBirth string `json:"date_of_birth"` -} diff --git a/services/internal/bridge-compliance-shared/protocols/compliance/receive.go b/services/internal/bridge-compliance-shared/protocols/compliance/receive.go deleted file mode 100644 index e45af3f587..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/compliance/receive.go +++ /dev/null @@ -1,29 +0,0 @@ -package compliance - -import ( - "encoding/json" - - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" -) - -// ReceiveRequest represents request sent to /receive endpoint of compliance server -type ReceiveRequest struct { - Memo string `form:"memo" valid:"required"` -} - -// Validate is additional validation method to validate special fields. -func (request *ReceiveRequest) Validate(params ...interface{}) error { - return nil -} - -// ReceiveResponse represents response returned by /receive endpoint -type ReceiveResponse struct { - helpers.SuccessResponse - // The AuthData hash of this memo. - Data string `json:"data"` -} - -// Marshal marshals ReceiveResponse -func (response *ReceiveResponse) Marshal() ([]byte, error) { - return json.MarshalIndent(response, "", " ") -} diff --git a/services/internal/bridge-compliance-shared/protocols/compliance/send.go b/services/internal/bridge-compliance-shared/protocols/compliance/send.go deleted file mode 100644 index 1912234555..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/compliance/send.go +++ /dev/null @@ -1,149 +0,0 @@ -package compliance - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/stellar/go/protocols/compliance" - "github.com/stellar/go/services/internal/bridge-compliance-shared/http/helpers" - "github.com/stellar/go/services/internal/bridge-compliance-shared/protocols" -) - -// SendRequest represents request sent to /send endpoint of compliance server -type SendRequest struct { - // Payment ID - used to resubmit auth request in case of `pending` response. - ID string `form:"id" valid:"required"` - // Source account ID - Source string `form:"source" valid:"required,stellar_accountid"` - // Sender address (like alice*stellar.org) - Sender string `form:"sender" valid:"required,stellar_address"` - // Destination address (like bob*stellar.org) - Destination string `form:"destination" valid:"required,stellar_address"` - // ForwardDestination - ForwardDestination *protocols.ForwardDestination `form:"forward_destination" valid:"-"` - // Amount destination should receive - Amount string `form:"amount" valid:"required,stellar_amount"` - // Code of the asset destination should receive - AssetCode string `form:"asset_code" valid:"optional,stellar_asset_code"` - // Issuer of the asset destination should receive - AssetIssuer string `form:"asset_issuer" valid:"optional,stellar_accountid"` - // Only for path_payment - SendMax string `form:"send_max" valid:"optional,stellar_amount"` - // Only for path_payment - SendAssetCode string `form:"send_asset_code" valid:"optional,stellar_asset_code"` - // Only for path_payment - SendAssetIssuer string `form:"send_asset_issuer" valid:"optional,stellar_accountid"` - // path[n][asset_code] path[n][asset_issuer] - Path []protocols.Asset `form:"path" valid:"-"` - // Extra memo - ExtraMemo string `form:"extra_memo" valid:"-"` -} - -// ToValuesSpecial converts special values from http.Request to struct -func (request *SendRequest) FromRequestSpecial(r *http.Request, destination interface{}) error { - var forwardDestination protocols.ForwardDestination - forwardDestination.Domain = r.PostFormValue("forward_destination[domain]") - forwardDestination.Fields = make(url.Values) - - err := r.ParseForm() - if err != nil { - return err - } - - for key := range r.PostForm { - matches := protocols.FederationDestinationFieldName.FindStringSubmatch(key) - if len(matches) < 2 { - continue - } - - fieldName := matches[1] - forwardDestination.Fields.Add(fieldName, r.PostFormValue(key)) - } - - if forwardDestination.Domain != "" && len(forwardDestination.Fields) > 0 { - request.ForwardDestination = &forwardDestination - } - - var path []protocols.Asset - - for i := 0; i < 5; i++ { - codeFieldName := fmt.Sprintf(protocols.PathCodeField, i) - issuerFieldName := fmt.Sprintf(protocols.PathIssuerField, 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 == "" { - path = append(path, protocols.Asset{}) - } else { - path = append(path, protocols.Asset{code, issuer}) - } - } - - request.Path = path - - return nil -} - -// ToValuesSpecial adds special values (not easily convertable) to given url.Values -func (request SendRequest) ToValuesSpecial(values url.Values) { - if request.ForwardDestination != nil { - values.Add("forward_destination[domain]", request.ForwardDestination.Domain) - for key := range request.ForwardDestination.Fields { - values.Add(fmt.Sprintf("forward_destination[fields][%s]", key), request.ForwardDestination.Fields.Get(key)) - } - } - - for i, asset := range request.Path { - values.Set(fmt.Sprintf(protocols.PathCodeField, i), asset.Code) - values.Set(fmt.Sprintf(protocols.PathIssuerField, i), asset.Issuer) - } -} - -// Validate is additional validation method to validate special fields. -func (request *SendRequest) Validate(params ...interface{}) error { - if request.Destination == "" && request.ForwardDestination == nil { - return helpers.NewMissingParameter("destination") - } - - asset := protocols.Asset{request.AssetCode, request.AssetIssuer} - err := asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", err.Error()) - } - - sendAsset := protocols.Asset{request.SendAssetCode, request.SendAssetIssuer} - err = sendAsset.Validate() - if err != nil { - return helpers.NewInvalidParameterError("asset", err.Error()) - } - - for i, asset := range request.Path { - err := asset.Validate() - if err != nil { - return helpers.NewInvalidParameterError(fmt.Sprintf("path[%d]", i), err.Error()) - } - } - - return nil -} - -// SendResponse represents response returned by /send endpoint -type SendResponse struct { - helpers.SuccessResponse - compliance.AuthResponse `json:"auth_response"` - // xdr.Transaction base64-encoded. Sequence number of this transaction will be equal 0. - TransactionXdr string `json:"transaction_xdr,omitempty"` -} - -// Marshal marshals SendResponse -func (response *SendResponse) Marshal() ([]byte, error) { - return json.MarshalIndent(response, "", " ") -} diff --git a/services/internal/bridge-compliance-shared/protocols/doc.go b/services/internal/bridge-compliance-shared/protocols/doc.go deleted file mode 100644 index 121a29d613..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Contains structs shared between bridge and compliance servers -package protocols diff --git a/services/internal/bridge-compliance-shared/protocols/main.go b/services/internal/bridge-compliance-shared/protocols/main.go deleted file mode 100644 index a854dc7c42..0000000000 --- a/services/internal/bridge-compliance-shared/protocols/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package protocols - -import ( - "net/url" - "regexp" -) - -var FederationDestinationFieldName = regexp.MustCompile(`forward_destination\[fields\]\[([a-z_-]+)\]`) - -const ( - PathCodeField = "path[%d][asset_code]" - PathIssuerField = "path[%d][asset_issuer]" -) - -// Asset represents asset -type Asset struct { - Code string `valid:"required" name:"asset_code" json:"code"` - Issuer string `valid:"optional" name:"asset_issuer" json:"issuer"` -} - -// ForwardDestination contains fields required to create forward federation request -type ForwardDestination struct { - Domain string `name:"domain"` - Fields url.Values `name:"fields"` -}