如何在多个智能合约之间实现共享公共数据服务

区块链资讯区块链研究实验室2020-09-11 10:15:18  阅读 -评论 0

在多个智能合约之间有许多可能的交互模型。在本文的示例中,我们设计了一个简单的生产者到消费者模型,以便我们能够集中讨论共享公共数据的主题。

典型的生产者与消费者模型 例如生产商可以是提供抵押贷款产品的银行。消费者可以是审计部门。
因此我们需要生产者和消费者模型:
1. 生产者和消费者以不同的速度工作
2. 生产商和消费者的工作时间不同
3. 生产者和消费者是不同的智能合约,由不同的团队或在不同的时间制定
4. 还有一系列其他原因
在这个模型中,生产者将产生一个数据对象,当消费者准备消费时,这个数据对象被发送给消费者进行消费。也就是说,两个智能合约必须处理相同的数据对象,但时间不同。
为此,我们将使用一个队列。
既然我们已经确定了公共队列数据必须在智能合约之间共享,让我们继续进行下去。
公共队列数据位置
在所有情况下,智能合约在以太坊虚拟机(EVM)中共享任何公共数据的唯一方法是,在提供公共数据空间的调用合约的上下文中调用智能合约的函数。这与通过Solidity的delegatecall功能实现的调用库函数相同。
下面我们针对公共队列数据位置依次考虑以下每个选项:
1) Common Data within a Producer contract
2) Common Data within a Consumer contract
3) Common Data within a Router contract
4) Common Data within a Queue contract
1)生产者智能合约中的公共数据
如果消费者尚未达成一致/实施/最终确定等,这可能会很有用。它还有利于多个消费者。

在生产者中查找公共队列数据 但是从上面的代码和数据位置图中可以看到,为了使Consumer代码能够访问公共队列数据,必须在Producer合约的上下文中调用它。
这也意味着,通常需要的任何消费者数据也必须保存在该上下文中,因为在生产者或消费者合约中分配存储数据是不可取的,因为这可能会覆盖另一个合约中的存储数据。
在某些情况下这可能是合适的,但在此示例中不适用。
2)消费者合约中的公共数据
如果生产者想要控制哪个消费者正在消费哪个物品,这可能很有用。它也有利于多个生产者。
但是与上面的说明类似,为了使生产者代码能够访问公共队列数据,必须在消费者合约的上下文中调用它。
上面的警告也适用。
同样在某些情况下,这可能是合适的,但在本例中不适用。
3)路由器合约中的公共数据
这需要路由合约。它为多个生产者和消费者提供了便利。它还有助于更改(或升级)生产者和消费者智能合约。

路由包含所有智能合约的数据 生产者和消费者可以是智能合约或库,但是它们的功能必须始终在路由环境中执行。
通常也需要在此环境中保留所有通常需要的生产者,消费者或路由数据,因为在生产者、消费者或路由器合约中分配存储数据是不可取的,因为这可能会覆盖其他合约中的存储数据。
该技术还要求智能合约使用公共数据进行约束,以便每个智能合约仅更改与其操作有关的数据,而不更改共享的公共数据中的其他任何内容。
4)队列合约中的公共数据
这也需要一个路由合约。它与前面的选项类似,但强制安全访问附加和删除项。

附加的队列合约控制对队列数据的访问 该解决方案稍微复杂一点:必须同时向生产者和消费者提供对队列合约的引用,并且队列合约还需要知道其地址,以检查是否允许访问。
这可能是大多数情况下的理想解决方案,但是为了使本例的代码更简单,我们将创建并测量选项(3),即路由合约中的公共数据。
共享公共数据的方法
在任何数量的Solidity智能合约和库之间共享路由器中的公共数据的方法,例如基于OpenZeppelin的“非结构化存储”的“ EIP-2535:钻石标准”和“代理合约和钻石的新存储布局”中所述的钻石存储代理”和其他文献。
该代码有效地为每个公共数据对象选择了随机存储插槽。每个对象的插槽均固定。由于插槽是从2²⁵⁶的虚拟地址空间中随机选择的,因此位置冲突的可能性很小。我们在本文中将不作进一步考虑。
每当任何合约或库需要访问公共Queue数据时,都会执行示例函数action()中显示的代码:
 function action() public {
        ...
        // Code Fragment (1)
        QueueData storage qds = queueData();
        ...
    }
这将获得对本例中我们共享的公共队列数据的专门引用。然后可以根据需要使用队列,如下所示。
queueData()函数为:
 // Code Fragment (2)
    function queueData() internal pure returns (QueueData storage) {
        return queueDataAt(QUEUE_DATA_LOCATION);
    }
这使用QueueDataAt()获得对QUEUE_DATA_LOCATION处的公共队列数据的引用,这看似简单的代码:
 // Code Fragment (3)
    function QueueDataAt(uint location) internal pure returns 
    (QueueData storage qds) {
        assembly { qds.slot := location }
    }
此函数返回QueueData存储变量。由于QueueData是结构,因此此函数返回引用(指向该结构的指针)。汇编语言语句只是将返回的引用的存储插槽设置为给定位置。这具有将位置参数(即uint)转换(有效地转换)到QueueData存储中的效果。在0.7.0和更低版本的编译器之间,访问插槽的程序集语法略有不同。
必须为QueueDataAt()提供一个固定的随机位置,在这种情况下为QUEUE_DATA_LOCATION,该位置采用以下方式编码:
// Code Fragment (4)
    uint constant QUEUE_DATA_LOCATION = 
                       uint(keccak256("queue.data.location"));
keccak256函数用于有效生成数据的随机位置。
如果解决方案中其他地方需要另一个队列,则需要将其放置在其他位置,这需要对keccak256函数使用不同的参数。
如果以上四个代码段存在于路由器或从属合约或库中的任何功能中,则可以访问相同的路由数据。
我们可以将这些代码片段组合在一起以产生:
 function action() public {
        ...
        // Code Fragment (1)
        QueueData storage qds = queueData();
        ...
    }
    // Code Fragments (2)(3)(4) combined
    function queueData() internal pure returns 
    (QueueData storage qds) {
        uint location = uint(keccak256("queue.data.location"));
        assembly { qds.slot := location }
    }
甚至:
 function action() public {
        ...
        // Code Fragments (1)(2)(3)(4) combine
        QueueData storage qds;
        uint location = uint(keccak256("queue.data.location"));
        assembly { qds.slot := location }
        ...
    }
我们将在下面的示例代码中使用这些组合的代码片段。
私有智能合约数据的方法
如上所述,由于在任何智能合约中分配传统存储数据是不可取的,因为它可能覆盖任何其他智能合约中的传统存储数据,因此任何私有智能合约数据也必须通过采用上述方法保持在路由环境中。
如果代码片段是智能合约或库专用的,则其他智能合约(例如路由智能合约)将无法访问数据。
示例实现
为了说明该方法,我们将在选项(3)中实现简单的Producer和Consumer模型,该模型在Router合约中的Common Data(选项(3))中进行,并着重于访问私有合约数据和共享Common队列数据的机制。

智能合约是:
The Producer contract
The Consumer contract
The Router contract
The QueueData library
The QueueDataLocation contract
私有智能合约数据为:
The Producer Data - within the Producer contract
The Consumer Data - within the Consumer contract
The Router Data - within the Router contract
共享的公共数据是:
The QueueData - within the QueueData library
生产者智能合约
生产者智能合约包含在路由合约的上下文中操纵其自己的私有合约数据以及将项目追加到公共队列数据中的代码。
contract Producer is QueueDataLocation {
    struct ProducerData {
        uint count;
    }
    function produce() public {
        // Code Fragment (1) for private Producer data
        ProducerData storage pds = producerData();
        pds.count++;
        // Code Fragment (1) for common queue data
        QueueData storage qds = QueueDataLocation.queueData();
        QueueDataLib.append(qds, pds.count);
    }
    // Code Fragments (2)(3)(4) combined for private Producer data
    function producerData() internal pure returns 
    (ProducerData storage pds) {
        uint location = uint(keccak256("produce.data.location"));
        assembly { pds.slot := location }
    }
}
稍后将显示QueueDataLocation合约。显示了访问两个不同数据结构(私有合约数据和共享公共数据)的代码片段(1)。代码片段(2),(3)和(4)与私有合约数据的方法相同。
消费者合约
消费者合约与生产者合约非常相似,包含在路由合约的环境中从公共队列数据中删除项目以及操纵其自己的私有合约数据的代码。
contract Consumer is QueueDataLocation {
    struct ConsumerData {
        uint total;
    }
    function consume() public returns (uint count) {
        // Code Fragment (1) for common queue data
        (bool success, uint item) = 
            QueueDataLib.remove(QueueDataLocation.queueData());
        if (success) {
            // Code Fragment (1) for private Consumer data
            ConsumerData storage cds = consumerData();
            cds.total += item;
            return cds.total;
        }
    }
    // Code Fragments (2)(3)(4) combined for private Consumer data
    function consumerData() internal pure returns 
    (ConsumerData storage cds) {
        uint location = uint(keccak256("consumer.data.location"));
        assembly { cds.slot := location }
    }
}
同样,代码片段(1)访问两个不同的数据结构,共享的公共数据和私有的合约数据。
路由合约
路由合约包含将调用路由到生产者合约和消费者合约的代码,并且还操纵其自己的私有合约数据。
contract Router is CallLib, QueueDataLocation {
    struct RouterData {
        Producer producer;
        Consumer consumer;
    }
    constructor(Producer producer, Consumer consumer, uint qSize) {
        // Code Fragment (1) for private Router data
        RouterData storage rds = routerData();
        rds.producer = producer;
        rds.consumer = consumer;
        // Code Fragment (1) for common queue data
        QueueDataLib.create(queueData(), qSize);
    }
    function produce() public {
        // Code Fragment (1) for private Router data
        callLib(address(routerData().producer));
    }
    function consume() public returns (uint total) {
        // Code Fragment (1) for private Router data
        RouterData storage rds = routerData();
        (bool ok, bytes memory bm) = callLib(address(rds.consumer));
        if (ok) {
            return abi.decode(bm, (uint256));
        }
    }
    // Code Fragment (2)(3)(4) combined for private Router data
    function routerData() internal pure returns 
    (RouterData storage rds) {
        uint location = uint(keccak256("router.data.location"));
        assembly { rds.slot := location }
    }
}
代码片段(1)显示访问两个不同的数据结构,其私有合约数据和共享公共数据。
提供了produce()和consume()公共函数来执行此解决方案的实际操作。请注意,produce()直接使用routerData()函数的返回值,而消耗()将其分配给变量。
这两个函数中也有一个技巧,我们将在下面揭示。
代码片段(2),(3)和(4)与私有合约数据的方法相同。
调用库(Call Library)
CallLib合约中提供的callLib()函数与文章“编码可升级的智能合约”和OpenZeppelin的“代理转发”中提供的后备函数类似。这是代码:
contract CallLib {
    function callLib(address adrs) internal returns 
    (bool, bytes memory) {
         assembly {            
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), adrs, 0, 
                                 calldatasize(), 0, 0)            
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {revert(0, returndatasize())}
            default {return (0, returndatasize())}
        }
    }
}
队列数据和库
该库包含在所有使用它的合约中。生产者合约附加项目,消费者合约删除项目,路由器合约设置队列大小。编译器将确保最终字节码中仅提供所需的那些功能。
QueueDataLocation合约
该合约提供公共队列数据位置。
contract QueueDataLocation {
    // Code Fragment (2)(3)(4) combined for common queue data
    function queueData() internal pure returns 
    (QueueData storage qds) {
        uint location = uint(keccak256("queue.data.location"));
        assembly { qds.slot := location }
    }
}
代码片段(2)、(3)和(4)按照共享公共数据的方法。
测试
这个简单、交互式、智能合约使部署人员能够生产和消费物品。
contract TestRouter {
    Router router;
    constructor() {
        Producer producer = new Producer();
        Consumer consumer = new Consumer();
        uint queueSize = 2;
        router = new Router(producer, consumer, queueSize);
    }
    function produce() public {
        router.produce();
    }
    function consume() public returns (uint) {
        return router.consume();
    }
}
耗气量
还构造了另一个合约,该合约包括继承的生产者和消费者合同,以方便进行天然气消耗量比较。从属合约使用相同的分配存储方法。
contract Combined is Consumer, Producer {
    constructor(uint32 queueSize) {
        QueueDataLib.create(queueData(), queueSize);
    }
}
智能合约实施
单个智能合约具有间接费用,因此我们可以预期建筑用气量将大于合并智能合约。

路由、生产商和消费者合约的天然气消耗量与联合合约 是的。由于构建的耗气量会随着时间摊销,所以这可能根本不是问题。
典型合约用法
路由合约的Produce()和消耗()公共功能的耗气量如何?

路由Produce()和Consume()函数的耗气量 正如预期的那样,使用callLib()重定向函数调用的成本(如前所示)是最小的。这可能是可以接受的额外灵活性。
进一步的可能性
路由器合约的Produce()和Consumer()公共函数使用callLib()来调用从属Producer和Consumer合约函数。路由和从属合约均使用Solidity功能签名(称为msg.sig)。这就是我们前面提到的技巧。目标功能必须具有与路由协定中的公共功能相同的名称和参数:

路由将功能调用重定向到下级合约 在这个例子中,不可能在路由协定中使用回退功能,因为目的地函数在不同的下级协定中。如果只有一个从属合约,那么可以使用fallback函数,正如“编码可升级智能合约”一文所述。
如果只有一种简单的方法让路由知道哪些契约支持哪些函数,那么它们的代码将由回退函数调用,如下所示:

使用回退函数调用下级协定的路由 结论
有很多原因可以将智能合约拆分为多份合约,并且需要共享公共数据并在安全的地方找到私有合约数据。
我们已经展示了使用一个简单的路由器(有一些注意事项)可以很容易地做到这一点。

声明:链世界登载此文仅出于分享区块链知识,并不意味着赞同其观点或证实其描述。文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。此文如侵犯到您的合法权益,请联系我们kefu@lianshijie.com

    参与讨论 (0 人参与讨论)

    相关推荐

    中信银行打造“区块链”信用证结算!

    中信银行打造“区块链”信用证结算!

    科技不会改变金融的实质,但却能让金融服务更高效,能让资金供、需方信息不对称的问题更好地解决。近期,中信银行首个区块链项目——基于区块链的国内信用证信息传输系统(简称BCLC)(一期)成功上线,这是国内银行业第一次将区块链技术应用于信用证结算领域。 据中信银行国际业务部总经理助理张栩青介绍,将现在流行的区块链技术应用在国内信用证中,改变了银行传统信用证业务模式,信用证的开立、通知、交单、承兑报文

    中国信息技术部门成立区块链研究实验室

    中国信息技术部门成立区块链研究实验室

    暴走时评:本月初,中国政府对国内的ICO和数字货币交易所的打击在世界范围内引起了强大反响,但政府已经多次声明不会将区块链与数字货币划等号,依然非常重视区块链技术在中国的发展。鉴于中国工业和信息化部成立了一个专门研究区块链的实验室,这一论调也得到了进一步的证实。 虽然中国政府最近在大力打击比特币交易所和ICO,但仍然致力于开发区块链在其他领域的潜力。 据财新网报道,中国工业和信息化部已经成立了一

     分布式账本中的生命科学

    分布式账本中的生命科学

    生物科学是医学领域涉及遗传研究,疾病预防和生活方式治疗(lifestyle treatments)的学科。它已经存在了很长时间,但区块链技术的基础设施应用给该学科提供了重大进步的可能性。 根据Pistoia Alliance进行的2016年6月份高级制药和生命科学领袖调查,83%的受访者表示,他们预计在五年内将全面采用区块链技术。 Pistoia Alliance是一个全球性的非营利组织,致

    区块链vs.核能:日本最大电力公司东京电力(TEPCO)寻求使用区块链减轻对核电的依赖

    区块链vs.核能:日本最大电力公司东京电力(TEPCO)寻求使用区块链减轻对核电的依赖

    东京电力公司 (TEPCO) 对于能源过度中心化的风险可以说绝不陌生。 也许最著名的就是2011年发生的福岛核电站事故,这个日本最大的能源公司如今正在寻求区块链技术来防止这种灾难再次发生。 然而,从使用微型风车的分布式风力发电到用于存储在电力成本低时购买的电力的智能电池,可替代能源项目一直以来都属于个人慈善事业。 然而,TEPCO风险投资部门主管Jeffrey Char认为区块链能够帮助为这

    继证监会发表代币发行声明之后,香港交易所Gatecoin将下线部分ICO币

    继证监会发表代币发行声明之后,香港交易所Gatecoin将下线部分ICO币

    经过一系列监管以及合规审查后,香港交易所Gatecoin将会下线那些被金融监管部门定性为"证券"的代币。 香港加密货币交易所Gatecoin透露,如果在该平台交易的ICO代币在法律上符合"证券"定义,他们就会下线这些代币。据巴比特上月报道,香港主要的金融监管部门证券及期货事务监察委员会(SFC)表达了对ICO这种日渐普及的募资模式的担忧。 尽管ICO中售卖的数字代币通常都被定义为虚拟商品,但

    IBM与超级账本共同加入去中心化身份基金会(DIF),推动创建区块链ID行业标准

    IBM与超级账本共同加入去中心化身份基金会(DIF),推动创建区块链ID行业标准

    IBM与超级账本已经签署协议加入去中心化身份基金会(DIF),这个于今年初成立的联盟旨在帮助推动基于区块链的ID系统的互操作性和标准。 这两个企业区块链大佬加入了这个有各种企业组成的团体,其中包括像微软和埃森哲这样的大企业,还有像Civic和Gem这样的创业公司,以及像uPort和Sovrin这样的开源项目。 DIF执行主管告诉Coindesk说: "这应该是一个信号,表明在这一领域有广泛的

    为打击人口贩卖,牙买加警方盯上了犯罪分子的比特币钱包

    为打击人口贩卖,牙买加警方盯上了犯罪分子的比特币钱包

    作为打击人口贩卖计划的一部分,牙买加警方已经开始行动,锁定了那些试图用比特币和数字支付来掩人耳目的犯罪分子。 越来越多的人口贩卖者都开始转向数字货币来帮助他们进行地下活动并接收非法活动所得,但牙买加警方已经盯上他们了。 牙买加的'大生意' 不幸的是,人口贩卖以及性奴市场规模十分庞大,预计涉资1500亿美元。在牙买加,大约有7000个妇女、儿童以及成年男性被奴役,他们的操控者出售奴役服务的价格

    深圳市将发布《深圳市扶持金融业发展若干措施》,奖励区块链、数字货币等金融创新

    10月9日,深圳市人民政府向各区人民政府,市政府直属各单位印发《深圳市扶持金融业发展若干措施》(以下简称"《若干措施》")。深圳市政府表示,此举是为进一步完善金融支持政策体系,吸引集聚优质金融资源,推动全市金融业可持续均衡发展,加快建设国际化金融创新中心。 《若干措施》共分五大项,33条。内容包括:坚持服务导向,优化金融政策环境;发展金融总部经济,鼓励金融总部企业做大做强;支持金融企业分支机构

    麦妖榜
    更新日期 2019-09-03
    排名用户贡献值
    1牛市来了30910
    2BitettFan24187
    3等待的宿命23810
    4区块大康20369
    5六叶树20310
    6linjm122719429
    7天下无双16192
    8lizhen00215280
    9让时间淡忘14586
    10yelanyi050511349
    返回顶部 ↑