Wecross项目源码解析

为了学习跨链的实现,找到了业界做的比较好的Wecross项目进行研究。目前网上关于Wecross项目源码解读的文章较少,写下这篇技术博客,既是为了自己学习,也是为了给大家一个参考。

Wecross项目的核心是:代理/桥接合约和跨链路由器。因此,阅读源码时,我们着重关心代理合约是如何部署在不同的链上,以及跨链路由器如何将跨链调用进行转发。在弄清楚上面两个任务后,我们还会继续弄清楚两阶段事务原子交易是如何实现的。

Wecross原理图


一、代理合约和桥接合约是如何部署的

研究项目源码中的interchain包,发现InterchainManager是关键的入口类,该类中包含注册任务的函数 registerTask(TaskManager taskManager),注册函数的核心逻辑如下:

        // 实例化一个工厂类
        InterchainTaskFactory interchainTaskFactory = new InterchainTaskFactory();
        // 初始化系统资源,调用的是该类中的一个私有方法
        SystemResource[] systemResources = initSystemResources();
        if (Objects.nonNull(systemResources) && systemResources.length > 0) {
              // 使用工厂方法加载job轮询任务(polling task) 
              Task[] tasks =
                    interchainTaskFactory.load(
                            systemResources,
                            InterchainDefault.INTER_CHAIN_JOB_DATA_KEY,
                            InterchainJob.class);
              // 注册上面加在的所有任务
              taskManager.registerTasks(tasks);
        }

轮询任务的注册完成之后,将交到Quartz进行定时任务执行。此时需要看到InterchainJob类,该类继承了Quartz的job,实现了execute方法,具体的轮询任务逻辑将在这里展示。

public class InterchainJob implements Job {

    // ...省略不重要代码

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 拿到job队列中的所有任务,只取出interchain类型的job
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        SystemResource systemResource = (SystemResource) dataMap.get(InterchainDefault.INTER_CHAIN_JOB_DATA_KEY);

        // 核心方法:这个方法在源码中是public,但根据实际的使用情况来看,应该是一个private才对
        // 下面将好好看下这个方法的逻辑
        handleInterchainRequests(systemResource);
    }
}

接着看调用到的 handleInterchainRequests(SystemResource systemResource) 方法:

    public void handleInterchainRequests(SystemResource systemResource) {

        // 从systemResource中拿到admin账户信息
        UniversalAccount adminUA;
        try {
            adminUA = systemResource.getAccountManager().getAdminUA();
        } catch (WeCrossException e) {
            logger.error("getAdminUA failed: ", e);
            return;
        }
        // 调用的该方法主要作用是为了获取 “跨链请求” 数组。由于该方法原理较为简单,此处不再单独粘出来读了。核心是异步调用hub获取跨链请求,并组装为字符串数组。
        String[] requests = getInterchainRequests(systemResource, adminUA);

        if (Objects.nonNull(requests)) {
            // 为了篇幅不要太长,一些异常处理的代码就不再展示了
            // 采用信号量机制,信号量的大小和“跨链请求”数组大小一致,确保并发时不会循环多了
            Semaphore semaphore = new Semaphore(requests.length, true);
            semaphore.acquire(requests.length);

            // 创建一个原子整型,防止并发导致的计数混乱
            AtomicInteger count = new AtomicInteger(0);
            String currentIndex = "0";
            String hubPath = systemResource.getHubResource().getPath().toString();

            for (String request : requests) {
                // 将request字符串构造成一个interchainRequest对象
                InterchainRequest interchainRequest = new InterchainRequest();
                interchainRequest.build(request);
                // 设置通用账户
                UniversalAccount userUA =
                            systemResource
                                    .getAccountManager()
                                .getUniversalAccountByIdentity(interchainRequest.getIdentity());
                // 根据interchainRequest以及账户信息构造interchainScheduler对象
                InterchainScheduler interchainScheduler = new InterchainScheduler();
                interchainScheduler.setInterchainRequest(interchainRequest);
                interchainScheduler.setSystemResource(systemResource);
                interchainScheduler.setAdminUA(adminUA);
                interchainScheduler.setUserUA(userUA);

                // 执行定时任务interchainScheduler,回掉函数中释放信号量
                interchainScheduler.start(
                            (exception) -> {
                                if (Objects.nonNull(exception)) {
                                    logger.error(
                                            "Failed to handle current inter chain request: {}, path: {}, errorMessage: {}, internalMessage: {}",
                                            request,
                                            hubPath,
                                            exception.getLocalizedMessage(),
                                            exception.getInternalMessage());
                                }
                                semaphore.release();
                            });
            }
            // 等待所有requesrs请求job完成
            semaphore.acquire(requests.length);
            // 更新request 索引
            if (!"0".equals(currentIndex)) {
                // 该函数逻辑为:将当前索引号组装为一个TransactionRequest对象,调用hub的异步事务发送,从而更新索引
                updateCurrentRequestIndex(systemResource, currentIndex, adminUA);
            }
        }

    }

通过阅读handleInterchainRequests方法,我们可以看出其核心逻辑是构造InterchainScheduler对象,并调用该对象的start方法完成request的执行,进一步的我们可以阅读InterchainScheduler源码,研究它是如何完成request的执行。

public class InterchainScheduler {


    public interface GetTransactionStateCallback {
        void onReturn(WeCrossException exception, String xaTransactionID, long xaTransactionSeq);
    }

    public interface InterchainCallback {
        void onReturn(WeCrossException exception);
    }

    // 执行方法调用了getXATransactionState,该方法核心参数是GetTransactionStateCallback接口的实现方法
    public void start(InterchainCallback callback) {
        // 实现的这个接口 @GetTransactionStateCallback
        transactionStateCallback = (getTransactionStateException, xaTransactionID, xaTransactionSeq) -> {
            // 省略非核心逻辑...
            // 构造真实UID, 并序列化为哈希256编码字符
            String realUid = Sha256Utils.sha256String((systemResource.getHubResource() + interchainRequest.getUid()).getBytes(StandardCharsets.UTF_8));
            
            // 取调用目标链顺序,若当前时间戳大于xaTransactionSeq则取当前时间戳
            long timestamp = System.currentTimeMillis();
            long callTargetChainSeq = timestamp > xaTransactionSeq ? timestamp : (xaTransactionSeq + 1L);

            // 调用 callTargetChain 方法,该方法将会调用目标链
            callTargetChain(realUid, xaTransactionID, callTargetChainSeq, (callTargetChainException, callTargetChainResult) -> {
                boolean state = true;
                String result = callTargetChainResult;
                // ...省略处理异常的相关代码...
                // 设置初始状态
                boolean finalState = state;
                String finalResult = result;
                getXATransactionState((getCallbackTransactionStateException, callbackXATransactionID, callbackXATransactionSeq) -> {
                    long newTimestamp = System.currentTimeMillis();
                    long callCallbackSeq = newTimestamp > callbackXATransactionSeq ? newTimestamp : (callbackXATransactionSeq + 1L);
                    // 好吧,又来一个回调函数,callCallback方法后面我们再看,此处我们省略另外三个参数,只关注该方法的最后一个回调函数的实现。
                    callCallback(..., (callCallbackException,errorCode,message,callCallbackResult) -> {
                        // 省略异常处理的相关代码,核心是调用注册回调结果的函数registerCallbackResult,后面我们再来分析该函数的逻辑
                        registerCallbackResult(..., registerCallbackResultException -> {
                            callback.onReturn(registerCallbackResultException);
                        }
                    }
                }
            }
        }
        
    }
}

在start方法中,反复使用了 public void getXATransactionState 方法,我们单独来看下这个方法做了些什么

public void getXATransactionState(GetTransactionStateCallback callback, String resourcePath) {

    // 省略异常处理相关代码,重点看向核心代码逻辑。构建TransactionRequest对象,并通过代理进行异步调用
    Path path = Path.decode(resourcePath);
    path.setResource(StubConstant.PROXY_NAME);
    Path proxyPath = new Path(path);
    Resource proxyResource = systemResource.getZoneManager().fetchResource(proxyPath);

    TransactionRequest transactionRequest = new TransactionRequest();
    transactionRequest.setArgs(new String[] {resourcePath});
    transactionRequest.setMethod(InterchainDefault.GET_XA_TRANSACTION_STATE_METHOD);
    transactionRequest.getOptions().put(Resource.RAW_TRANSACTION, true);

    proxyResource.asyncCall(transactionRequest,  adminUA, (transactionException, transactionResponse) -> {
        // 省略判断异常错误码的if逻辑...
        // 获取相应的调用结果
        String result = transactionResponse.getResult()[0].trim();
        String[] states = result.split(InterchainDefault.SPLIT_REGEX);
        callback.onReturn(null, states[0], Long.parseLong(states[1]));
    }
}

显然,getXATransactionState方法的逻辑很简单,即组装一个事物请求对象,然后通过proxy进行异步调用。那么,proxyResource.asyncCall方法的调用逻辑我们也需要了解,查看源码可以看到,该方法在Resource类中,该方法核心逻辑如下:

            // driver即目标区块链的stub实现,在wecross项目中仅提供了相应的接口,driver的实现需要以插件的形式加载进来
            // 第三个参数为true表示由proxy进行调用
            driver.asyncCall(
                    context,
                    request,
                    true,
                    chooseConnection(),
                    (transactionException, transactionResponse) -> {
                        if (logger.isDebugEnabled()) {
                            logger.debug(
                                    "asyncCall response: {}, exception: {}",
                                    transactionResponse,
                                    transactionException);
                        }
                        callback.onTransactionResponse(transactionException, transactionResponse);
                    });

看完上述方法,我们还需要关注registerCallbackResult方法,该方法将调用结果进行注册。注意,我们始终需要关注wecross官方给出的整体架构图,注册跨链调用请求和查询调用结果均是由桥接合约(hub)完成的。因此,该方法的核心逻辑一定是通过hubProxy调用完成的。下面看下该方法的核心逻辑:

public void registerCallbackResult(String xaTransactionID,long xaTransactionSeq,int errorCode,String message,String result,RegisterResultCallback callback) {

    Resource hubResource = systemResource.getHubResource();
    TransactionRequest transactionRequest = new TransactionRequest();
    transactionRequest.setArgs(
        new String[] {
                        interchainRequest.getUid(),
                        xaTransactionID,
                        String.valueOf(xaTransactionSeq),
                        String.valueOf(errorCode),
                        message,
                        result
                    });
    transactionRequest.setMethod(InterchainDefault.REGISTER_CALLBACK_RESULT_METHOD);
    hubResource.asyncSendTransaction(transactionRequest,adminUA,(transactionException, transactionResponse) -> {
        // 省略判断异常错误代码相关的代码,当注册成功即将回调返回
        callback.onReturn(null);
    }

}

显然,在registerCallbackResult方法中,我们也需要关注hubResource.asyncSendTransaction方法。通过阅读源码发现,该方法和上面介绍的driver.asyncCall方法类似,也是由stub来实现的。

driver.asyncSendTransaction(
                    context,
                    request,
                    true,
                    chooseConnection(),
                    (transactionException, transactionResponse) -> {
                        if (logger.isDebugEnabled()) {
                            logger.debug(
                                    "asyncSendTransaction response: {}, exception: ",
                                    transactionResponse,
                                    transactionException);
                        }
                        callback.onTransactionResponse(transactionException, transactionResponse);
                    });

弄清楚上面这几个方法,我们再看到InterchainScheduler.start方法中callTargetChain和callCallback两个方法的实现。先看callTargetChain,该方法的作用和名字一样,是调用目标区块链,核心代码仍然和上面说到的hub/proxy调用是一样的,都是通过asyncSendTransaction实现。

public void callTargetChain(String uid,String xaTransactionID,long xaTransactionSeq,CallTargetChainCallback callback) {

    TransactionRequest transactionRequest = new TransactionRequest();
    transactionRequest.setArgs(
                    new String[] {objectMapper.writeValueAsString(interchainRequest.getArgs())});
            transactionRequest.setMethod(interchainRequest.getMethod());
    transactionRequest.getOptions().put(StubConstant.TRANSACTION_UNIQUE_ID, uid);

    // 设置了一个超时器,用于调用时的超时异常处理,异常代码省略...
    Timeout callTargetChainTimeout = timer.newTimeout(...);

    // 通过stub的asyncSendTransaction实现目标链调用
    resource.asyncSendTransaction(
                        transactionRequest,
                        userUA,
                        (transactionException, transactionResponse) -> {
                            // 省略错误码和异常情况处理代码...在没有异常情况下,将执行callback的回调
                                callTargetChainTimeout.cancel();
                                if (Objects.isNull(transactionResponse.getResult())
                                        || transactionResponse.getResult().length == 0) {
                                    callback.onReturn(null, "[]");
                                } else {
                                    callback.onReturn(null, transactionResponse.getResult()[0]);
                                }
                        });
    
}

本来还需要看一下callCallback的源码,但是这个方法的逻辑和callTargetChain方法基本一致,核心代码也是通过asyncSendTransaction实现。

至此,基本上也理清楚了interchain包的核心逻辑,大致上可以用下图表示

 代理/桥接合约的实现在源码中基本上是interchain包实现的,阅读这部分代码并弄清楚内在的逻辑并不难,因为这部分代码基本上是提供大量接口为主,而非具体的实现(具体的实现由stub对应的目标区块链自行适配,目前官方开发了BCOS和Fabric的stub实现)。后面我讲继续阅读路由的实现代码,在项目中应该是routine包下的htlc和XA。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值