锚点的任务是处理监管合规性,如反洗钱(AML)。 为此,您应使用POCChain合规协议,这是一种交换合规信息的标准方式,并预先批准与其他金融机构的交易。

您可以编写自己的服务器以符合合规性协议,但Stellar.org还提供了一个合规性服务器,可以为您完成大部分工作。

您的分布服务器会联系您的合规性服务器,以便在发送之前授权该交易。 您的合规性服务器使用合规性协议来清除与收件人的合规性服务器的交易,然后让桥接服务器知道交易可以发送。

当另一个合规性服务器与您联系以清除交易时,将使用一系列回调来与您一起检查信息。 稍后,当您的分布服务器收到某个交易时,它会与您的合规性服务器联系以验证它是否已清除。

创建数据库

合规性服务器需要MySQL或PostgreSQL数据库才能保存交易和合规性信息。 创建一个名为stellar_compliance的新数据库和一个用户来管理它。 您不需要添加任何表格; 服务器包含配置和更新数据库的命令

下载并配置Compliance Server

首先下载适用于您平台的最新Compliance服务器,然后在任何您喜欢的地方安装可执行文件,在同一目录中,创建名为config_compliance.toml的文件。 这将存储合规性服务器的配置。 它应该看起来像:

external_port = 8003
internal_port = 8004
# Set this to `true` if you need to check the information of a person receiving
# a payment you are sending (if false, only the sender will be checked). For
# more information, see the callbacks section below.
needs_auth = false
network_passphrase = "Test SDF Network ; September 2015"

[database]
type = "mysql" # Or "postgres" if you created a PostgreSQL database
url = "dbuser:dbpassword@/stellar_compliance"

[keys]
# This should be the secret seed for your base account (or another account that
# can authorize transactions from your base account).
signing_seed = "SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ"
encryption_key = "SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ"

[callbacks]
sanctions = "http://localhost:8005/compliance/sanctions"
ask_user = "http://localhost:8005/compliance/ask_user"
fetch_info = "http://localhost:8005/compliance/fetch_info"

# The compliance server must be available via HTTPS. Specify your SSL
# certificate and key here. If the server is behind a proxy or load  balancer
# that implements HTTPS, you can omit this section.
[tls]
certificate_file = "server.crt"
private_key_file = "server.key"

配置文件列出external_portinternal_port。 外部端口必须可公开访问。 这是其他组织将联系的端口,以确定您是否接受付款。

内部端口不应公开访问。 它是您通过其启动合规性操作并传输私人信息的端口。 您可以通过防火墙,代理或其他方式保护此端口的安全。

您还需要告诉您的分布服务器您现在拥有可以使用的合规性服务器。 使用合规性服务器内部端口的地址更新config_bridge.toml

port = 8001
horizon = "https://horizon-testnet.stellar.org"
network_passphrase = "Test SDF Network ; September 2015"
compliance = "https://your_org.com:8004"

# ...the rest of your configuration...

实施合规性回调

在服务器配置文件中,有三个回调URL,非常类似于桥接服务器的回调URL。 它们是HTTP POST URL,将发送表单编码数据:

  • fetch_info被发送一个federation地址(如tunde_adebayo*your_org.com),并应返回另一个组织执行合规性检查所需的所有信息。 它可以是您认为合理的任何数据,并且必须格式化为JSON。

    当您发送付款时,将调用该付款以获取有关发送付款的客户的信息,以便将其发送给接收组织。 收到付款时,如果发送组织已请求接收方的信息进行自己的合规性检查(基于needs_auth配置),则会调用该付款。

    @POST
    @Path("compliance/fetch_info")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    public Response fetchInfo(
      @FormParam("address") String address) {
    
      String friendlyId = address.split("\\*", 2)[0];
    
      // You need to create `accountDatabase.findByFriendlyId()`. It should
      // find customers by their POCChain account and return account information.
      try {
        Account account = accountDatabase.findByFriendlyId(friendlyId);
        return Response.ok(
          // This can be any data you determine is useful and is not limited to
          // these three fields.
          Json.createObjectBuilder()
            .add("name", account.fullName)
            .add("address", account.address)
            .add("date_of_birth", account.dateOfBirth)
            .build())
          .build();
        )
      } catch (Exception error) {
        System.out.println(
          String.format("Could not find account: %s", address));
        return Response.status(500).entity(error.getMessage()).build();
      }
    }
    
  • sanctions 制裁会提供有关向您或您的某个客户付款的人的信息。 这与发送服务器从其自己的fetch_info回调中收到的数据相同。 它产生的HTTP响应代码指示是否接受支付(状态200),拒绝(状态403),或者是否需要额外的处理时间(状态202)。

    import java.io.*;
    import javax.json.Json;
    import javax.json.JsonObject;
    import javax.json.JsonReader;
    
    @POST
    @Path("compliance/sanctions")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    public Response sanctions(@FormParam("sender") String sender) {
      JsonReader jsonReader = Json.createReader(new StringReader(sender));
      JsonObject senderData = jsonReader.readObject();
      jsonReader.close();
    
      // You need to create a function to check whether there are any sanctions
      // against someone.
      Permission permission = sanctionsDatabase.isAllowed(
        senderData.getString("name"),
        senderData.getString("address"),
        senderData.getString("date_of_birth"));
    
      // In this example, we're assuming `isAllowed` returns a Permissions enum
      // that indicates whether someone is Allowed, Denied, or Unknown. Your
      // systems may work differently; just return the same HTTP status codes.
      if (permission.equals(Permission.Allowed)) {
        return Response.ok().build();
      }
      else if (permission.equals(Permission.Denied)) {
        return Response.status(403).build();
      }
      else {
        // If you need to wait and perform manual checks, you'll have to implent
        // a way to do that as well.
        notifyHumanForManualSanctionsCheck(senderData);
        // The value for `pending` is a time to check back again in seconds.
        return Response.accepted(
          Json.createObjectBuilder()
            .add("pending", 3600)
            .build())
          .build();
      }
    }
    
  • 如果发件人已请求有关接收者的信息,则在收到付款时会调用ask_user。 它的返回码表示你是否会发送该信息(然后调用fetch_info来实际获取信息)。 它会发送有关付款和发件人的信息。

    @POST
    @Path("compliance/ask_user")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    public Response askUser(@FormParam("sender") String sender) {
      JsonReader jsonReader = Json.createReader(new StringReader(sender));
      JsonObject senderData = jsonReader.readObject();
      jsonReader.close();
    
      // You can do any checks that make sense here. For example, you may not
      // want to share information with someone who has sanctions as above:
      Permission permission = sanctionsDatabase.isAllowed(
        senderData.getString("name"),
        senderData.getString("address"),
        senderData.getString("date_of_birth"));
    
      if (permission.equals(Permission.Allowed)) {
        return Response.ok().build();
      }
      else if (permission.equals(Permission.Denied)) {
        return Response.status(403).build();
      }
      else {
        // If you need to wait and perform manual checks, you'll have to create
        // a way to do that as well.
        notifyHumanForManualInformationSharing(senderData);
        // The value for `pending` is a time to check back again in seconds.
        return Response.accepted(
          Json.createObjectBuilder()
            .add("pending", 3600)
            .build())
          .build();
      }
    }
    

为了简单起见,我们将所有三个回调添加到我们用于分布服务器回调的同一服务器上。 但是,您可以在基础结构中有意义的任何服务上实现它们。 只需确保您的配置文件中的URL可以访问它们。

更新 POCC.toml

当其他组织需要联系您的合规性服务器以授权向您的某个客户付款时,他们会查询您的域的POCC.toml文件以获取该地址,就像查找federation服务器时一样。

对于合规性操作,您需要在POCC.toml中列出两个新属性:

FEDERATION_SERVER = "https://www.your_org.com:8002/federation"
AUTH_SERVER = "https://www.your_org.com:8003"
SIGNING_KEY = "GAIGZHHWK3REZQPLQX5DNUN4A32CSEONTU6CMDBO7GDWLPSXZDSYA4BU"

AUTH_SERVER是合规性服务器的外部端口的地址。 与您的联合服务器一样,它可以是您喜欢的任何URL,但它必须支持HTTPS并使用有效的SSL证书。[1]

SIGNING_KEY是与您的合规性服务器配置中为signing_seed指定的秘密种子相匹配的公钥。 其他组织将使用它来验证消息是否实际由您发送。

启动服务器

在第一次启动服务器之前,需要创建数据库中的表。 使用--migrate-db参数运行合规性服务器将确保将所有内容设置为:

./compliance --migrate-db

每次将合规性服务器更新为新版本时,都应该再次运行此命令。 如果需要更改任何内容,它将升级您的数据库。

现在您的数据库已完全设置,您可以通过运行以下命令启动合规性服务器:

./compliance

试试看

既然您已经设置了合规性服务器并准备好验证交易,那么您需要通过向运行自己的合规性和federation服务器的人员发送付款来对其进行测试。

最简单的方法是简单地测试从您自己的客户到另一个客户的付款。 您的合规性,federation身份验证和分布服务器将执行交易的发送方和接收方。

通过您的分布服务器发送付款,但这一次,使用发件人和收件人的federation地址以及extra_memo [2]来触发合规性检查:

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.util.ArrayList;
import java.util.List;

public class PaymentRequest() {
  public static void main(String [] args) {
    HttpPost paymentRequest = new HttpPost("http://localhost:8001/payment");

    List<NameValuePair> params = new ArrayList<NameValuePair>();
    params.add(new BasicNameValuePair("id", "unique_payment_id"));
    params.add(new BasicNameValuePair("amount", "1"));
    params.add(new BasicNameValuePair("asset_code", "USD"));
    params.add(new BasicNameValuePair("asset_issuer", "GAIUIQNMSXTTR4TGZETSQCGBTIF32G2L5P4AML4LFTMTHKM44UHIN6XQ"));
    params.add(new BasicNameValuePair("destination", "amy*your_org.com"));
    params.add(new BasicNameValuePair("source", "SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ"));
    params.add(new BasicNameValuePair("sender", "tunde_adebayo*your_org.com"));
    // `extra_memo` is required for compliance (use it instead of `memo`)
    params.add(new BasicNameValuePair("extra_memo", "Test transaction"));

    HttpResponse response = httpClient.execute(paymentRequest);
    HttpEntity entity = response.getEntity();
    if (entity != null) {
      String body =  EntityUtils.toString(entity);
      System.out.println(body);
    }
  }
}

要进行更实际的测试,请在不同的域中设置桥接,联合和合规性服务器的副本,然后向其发送付款!