当前位置: 首页 > news >正文

区块链应用第1讲:基于区块链的智慧货运平台

基于区块链的智慧货运平台

网络货运平台已经比较成熟,提供了给货源方提供找司机的交易匹配方案;其中包含这几个角色:货主、承运人(司机、车队长)、监管机构、平台。司机要想接单,依赖于多个中心化的第三方平台,且三方平台数据互不通,造成了如下几个问题用户身份重复上传,承运人需要在各个货运平台注册,上传相同的身份证、驾驶证、行驶证、道路运输证信息;运单信息人为瞒报误报,如何保证监管数据的准确性,减少人为瞒报误报资金风险,如何保证资金管理公开透明,防止平台卷款跑路,随着货运平台的发展,以上问题亟待解决,以保障行业健康发展。

文章目录

  • 基于区块链的智慧货运平台
    • 1、背景
    • 2、解决方案
    • 3、数字身份简介
    • 问题1:哪些场景会导致VC创建或校验失败?
    • 问题2:如果相同用户相同属性多次创建可验性证明,那么得到的证书信息都能被验证吗?
    • 4、智慧货运平台功能点
      • 4.1、参与方
      • 4.2、流程图
      • 4.3、表结构设计
      • 4.4、核心功能
    • 5、Solidity源码
    • 6、项目代码及测试用例

1、背景

网络货运平台已经比较成熟,提供了给货源方提供找司机的交易匹配方案;其中包含这几个角色:货主、承运人(司机、车队长)、监管机构、平台。

其主要流程大体如下

  • 1、司机入驻平台,需要上传身份证,驾驶员信息,车辆信息;
  • 2、接下来货主在平台发布货源,然后司机进行抢单或者接单或者转让运单,货主也对这笔运单进行调价处理;
  • 3、在完成运单后,需要上报信息给平台,然后货主将应付款项支付给司机,且资金流水需要上报给平台,并将佣金支付给平台;
  • 4、监管机构需监管运单整体生命周期,读取驾驶员信息,车辆信息,订单信息和流水信息。

司机要想接单,依赖于多个中心化的第三方平台,且三方平台数据互不通,造成了如下几个问题

  • 1、用户身份重复上传,承运人需要在各个货运平台注册,上传相同的身份证、驾驶证、行驶证、道路运输证信息;

  • 2、运单信息人为瞒报误报,如何保证监管数据的准确性,减少人为瞒报误报

  • 瞒报: 人/车信息不匹配

    误报:司机各类证书已过期,但是还能在平台接单

  • 3、资金风险,如何保证资金管理公开透明,防止平台卷款跑路

随着货运平台的发展,以上问题亟待解决,以保障行业健康发展。

2、解决方案

为了满足上述需求,通过区块链技术打造一个智慧货运平台 ,通过智能合约将资金管理权利合理分配,实现司机信息可信认证,并保证运单数据可溯源,防篡改,可信上链,提高信息监管的效率和安全性,减少欺诈和错误。

  • 对于用户重复注册和校验问题,由数字身份合约解决;

    使用DID+VC 实现去中心化验证

  • 对于运单信息人为瞒报误报问题,运单的可信上链由运单合约完成;

    减少或消除中介机构的业务场景,打造去中心化的市场

  • 对于资金风险问题,平台只有审核权限,司机的运费由监管机构托管,平台只能收取佣金,由钱包合约完成

  • 原来我们完成支付需要对接易宝、支付宝、微信、或者直接对接银行,现在通过区块链完成交易

3、数字身份简介

原来:中心化身份: 由单一的权威机构进行管理和控制的,现在互联网上的大多数身份还是中心化身份。

现在:以用户为中心的身份: 重点集中在去中心化上,通过授权和许可进行身份数据的共享,例如OpenID。

数字身份标识-DID(去中心化身份标识)

DID标识

去中心化:使用分布式储存技术

身份标识:在分布式系统中为用户身份生成唯一的编号

  • Identifier 一般为用户地址

image-20241103092002015

DID文档

保存了与DID验证相关的密钥信息和验证方法

{"@context": ["https://www.w3.org/ns/did/v1","https://w3id.org/security/suites/ed25519-2020/v1"]"id": "did:example:123456789abcdefghi",// 里面是个数组,对应多个公钥"authentication": [{"id": "did:example:123456789abcdefghi#keys-1","type": "Ed25519VerificationKey2020","controller": "did:example:123456789abcdefghi","publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"}]......
}

可验证声明(VC DID的生态技术 主要包含3大角色和3大要素)

可验证声明(Verifiable Credential)提供了一种规范来描述实体所具有的某些属性,实现基于证据的信任。DID持有者,可以通过可验证声明,向其他实体(个人、组织、具体事物等)证明自己的某些属性是可信的。

在DID生态体系内,主要有用户、发行方、使用方三种角色

  • 发行方(Issuer 监管): 可发行数字凭证的人/组织。例如:高校可为某个学生颁发数字毕业证,那么这个高校便是一个发证方,公安局给居民颁发身份证,交通运输局给承认人颁发驾驶证、行驶证。

    • 身份证(公安局) 驾驶证和行驶证(交管部门)

  • 用户(User 也称为holder 承运人): 拥有链上数字身份的任何人/组织/实物。任何实体对象都可通过开发者的项目去创建、管理自己的DID。

    • 拥有凭证并将其存储在数字钱包中的人

  • 验证方(Verifier 货主): 也称为业务方,指使用数字凭证的人/组织,验证方在经用户授权后,可对用户的身份或其数字凭证进行验证。例如:企业录取某个人的时候,要对其高校毕业证进行验证,那么这个企业便是一个验证方。

    • 验证或鉴别证书的个人或组织,通过扫描二维码来启动验证过程

可验证证书3大要素

  • Credential Metadata(证书元数据): 由w3c定义的JSON-LD规范 它可能由颁发者以加密方式签名,并包含证书标识符以及有关证书本身的属性,如到期日期和颁发者。
  • claims(声明):对证书主题(如某人的员工编号和职务)提出的一组不可篡改的声明。
  • proof(s)(证明):允许人们以密码学方式验证:
    • 数据来源(如发行人是谁)
    • 数据没有被篡改
// 一个简单的VC示例(JSON-LD格式)返回数据如下:
{"@context": ["https://www.w3.org/2018/credentials/v1","https://www.w3.org/2018/credentials/examples/v1"],"type": ["VerifiableCredential", "AlumniCredential"],"id": "http://example.edu/credentials/1872","issuer": "https://example.edu/issuers/565049","issuanceDate": "2010-01-01T19:23:24Z","expirationDate":"2010-01-01T19:23:24Z","credentialSubject": {"id": "did:example:ebfeb1f712ebc6f1c276e12ec21","alumniOf": {"id": "did:example:c276e12ec21ebfeb1f712ebc6f1","name": [{"value": "Example University","lang": "en"}, {"value": "Exemple d'Université","lang": "fr"}]}},"proof": {"type": "RsaSignature2018","created": "2017-06-18T21:19:10Z","proofPurpose": "assertionMethod","verificationMethod": "https://example.edu/issuers/565049#key-1","jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM"}
}

JSON-LD文档

特点:是一个从未更新的静态文档,因此可以在客户端下载和缓存

作用:用于验证声明的主题属性在JSON-LD中是否存在,而且主题属性只能小于等于JSON-LD定义的属性范围

{"@context": {"Carrier": {"@id": "https://schema.affinidi.io/TCarrierV1R0.jsonld","@context": {"@version": 1.1,"@protected": true}},"name": {"@id": "schema-id:name","@type": "https://schema.org/Text"},"plateNo": {"@id": "schema-id:plateNo","@type": "https://schema.org/Text"},"plateColor": {"@id": "schema-id:plateColor","@type": "https://schema.org/Text"},"vehicleType": {"@id": "schema-id:vehicleType","@type": "https://schema.org/Text"},"owner": {"@id": "schema-id:owner","@type": "https://schema.org/Text"},"registerDate": {"@id": "schema-id:registerDate","@type": "https://schema.org/Date"}}
}

问题1:哪些场景会导致VC创建或校验失败?

发行VC失败

1、用于可验证证明的属性不在JsonLd定义的范围内

2、jsonld无法下载

3、凭证类型为空

4、发行方秘钥信息没有对应

5、发行时间小于当前时间,或者超期时间小于当前时间

校验VC失败

1、用于可验证证明的属性不在JsonLd定义的范围内

2、凭证属性值被修改

3、超过了认证的有效期

问题2:如果相同用户相同属性多次创建可验性证明,那么得到的证书信息都能被验证吗?

经过代码验证,生成的jws不同,但是都可以用于身份验证

4、智慧货运平台功能点

4.1、参与方

  • 货主:发布运单的个人或组织
  • 承运人(司机、车队长):提供运力的个人或组织
  • 监管机构:具有一定公信力的机构,如:交通运输部-协会
  • 货运平台:负责审批司机身份,提供交易匹配能力,审批运单状态(发布、调价、完结) 的机构

4.2、流程图

image-20241107143420819

4.3、表结构设计

# 账户表,主要用来记录秘钥信息,角色信息,账户余额,数字身份信息 目前共5条数据,两承运人,一个货主,一平台,一个监管机构
CREATE TABLE `account` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',`name` varchar(16) NOT NULL COMMENT '账户名',`private_key` varchar(64) NOT NULL COMMENT '私钥',`public_key` varchar(256) NOT NULL COMMENT '公钥',`did` varchar(64) NOT NULL COMMENT 'did合约地址',`role` tinyint(4) DEFAULT NULL COMMENT '1-承运人 2-货主 3-平台 4-监管',`account_no` varchar(64) NOT NULL COMMENT '账户编号',`balance` bigint(20) NOT NULL DEFAULT '0' COMMENT '余额',`ext_info` json DEFAULT NULL COMMENT '账户扩展信息',`vc_info` mediumtext COMMENT '数字身份信息',`is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '0-正常 1-删除',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_account_no` (`account_no`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COMMENT='账户表'# 账户流水表 采用双边记账法,记录充值,支付,提现流水,记录动账金额和余额,账户from to信息
CREATE TABLE `account_flow` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',`flow_no` varchar(64) NOT NULL COMMENT '记录唯一编号',`account_no` varchar(64) NOT NULL COMMENT '账户编号',`category` tinyint(4) unsigned NOT NULL COMMENT '10-充值 20-支付 30-支付退款 40-分账 50-分账退还  60-结算 70-提现',`category_name` varchar(32) NOT NULL COMMENT '类目名称',`amount` decimal(18,2) unsigned NOT NULL COMMENT '动账金额',`transaction_type` varchar(32) NOT NULL COMMENT 'TRANSACTION_IN/TRANSACTION_OUT',`balance` decimal(18,2) unsigned NOT NULL COMMENT '动账后,对应账户余额',`from_account_no` varchar(64) DEFAULT NULL COMMENT '动账来源账户no',`to_account_no` varchar(64) DEFAULT NULL COMMENT '动账去向账户no',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_flow_no` (`flow_no`),KEY `idx_accountno` (`account_no`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='账户动账记录表'# 运单主表 记录运单详情,运单状态及对应时间,点位信息,承运人信息,货主信息等
CREATE TABLE `order_waybill` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',`waybill_no` bigint(20) unsigned NOT NULL COMMENT '运单编号',`goods_name` varchar(128) NOT NULL COMMENT '货物名称',`goods_weight` decimal(10,3) DEFAULT NULL COMMENT '货物毛重 单位吨',`waybill_state` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '运单状态 0已创建 10已发布 20已接单 40已送达 60已完成 99已取消',`waybill_audit_state` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '运单审核状态 0待审核 1已审核',`biz_type` tinyint(4) unsigned NOT NULL COMMENT '业务类型 1集装箱运单 2托运运单',`loading_addr` varchar(255) NOT NULL COMMENT '装货点地址',`receipt_addr` varchar(255) NOT NULL COMMENT '收货点地址',`price` decimal(18,2) NOT NULL COMMENT '外发价格,结给司机的运费,单位元',`consigner_addr` varchar(64) DEFAULT NULL COMMENT '货主地址',`consigner_name` varchar(128) NOT NULL COMMENT '托运人,个人时为姓名,企业时为公司名称',`driver_addr` varchar(64) DEFAULT NULL COMMENT '司机地址',`driver_name` varchar(64) DEFAULT NULL COMMENT '司机姓名',`plate_no` varchar(64) DEFAULT NULL COMMENT '车辆车牌号',`plate_color` int(11) unsigned DEFAULT NULL COMMENT '车辆车牌颜色 1黄牌 2绿牌 3黄绿牌',`publish_time` datetime DEFAULT NULL COMMENT '发单时间',`confirm_time` datetime DEFAULT NULL COMMENT '接单时间',`despatch_time` datetime DEFAULT NULL COMMENT '送货时间',`receipt_time` datetime DEFAULT NULL COMMENT '收货时间',`create_user` bigint(20) unsigned NOT NULL COMMENT '创建人',`create_user_name` varchar(128) NOT NULL DEFAULT '' COMMENT '创建人姓名',`update_user` bigint(20) unsigned DEFAULT NULL COMMENT '更新人',`update_user_name` varchar(128) DEFAULT '' COMMENT '更新人姓名',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `waybill_no` (`waybill_no`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='运单主表'

4.4、核心功能

数字身份认证

  • did管理1、创建承运人DID 2、创建监管机构DID 3、货主身份DID,4、网货平台身份DID
  • 可验证身份凭证 1、由监管账户签发VC 2、验证VC

流程

调用管理接口

  • 司机1 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 角色1 100

  • 司机2 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1 角色1 101

  • 监管机构 0xcda9a3a5e849d28b6ddbea8b1cd348df7726c312

  • 货主1 0xfd917601078bb946bacba9a46f66012b9b4a7321 角色2,200

  • 平台 0xcf33a0eef94c244b2d926d3dbba2666b51332b75

为司机1和司机2签发证书

  • 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 司机1
  • 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1 司机2

验证VC

合约地址:0xfd8fdffba51cf0cb4ed504337c0ea3dc9440098a

钱包合约:每个参与方必须先注册角色并开通钱包,钱包合约给各方分配账户地址与橘色,地址与余额建立映射管理

  • 货主可以通过钱包查数字资产余额、充值、提现、转账;

  • 平台可以通过钱包查看数字资产余额、提现;

  • 司机可以通过钱包查看数字资产余额、提现;

    • 规则:不支持充值和转账

流程如下

  • 司机1 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 角色1 100

  • 司机2 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1 角色1 101

  • 货主1 0xfd917601078bb946bacba9a46f66012b9b4a7321 角色2,200

  • 平台 0xcf33a0eef94c244b2d926d3dbba2666b51332b75 角色3 300

  • 监管 0xcda9a3a5e849d28b6ddbea8b1cd348df7726c312 角色4 400

给货主充值100元,给司机1充值200元

  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 充值220
  • 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 充值200 --> 报错,不允许充值

给货主提现10元

  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 提现10 --》剩余210元

货主1给司机1转账10元

  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 --》0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 转账10 --》货主1 200元 司机1 10元

司机提现10元

  • 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 提现10元 司机1剩余0元

运单合约:由真实的货运业务抽象而成,货主发布运单,货运平台对运单进行审批,承运人接单。

规则如下

1、创建运单 货主发起运单

2、审核运单(已发布) 货运平台审核运单

3、运单已接单 修改运单状态为已发布

规则:司机只能有一笔在途运单,暂不支持拼单

4、运单调价 修改运单价格,修改运单审核状态

规则:降价需要审核,涨价无需审核

5、运单转单 修改运单承运人信息

规则:只支持车队内部承运人转让

6、运单已送达 修改运单状态已送达

司机操作

7、货主审核通过 完成运单,修改运单状态已完成,货主将将数字资产打入承运人钱包,货运平台收到佣金

货主操作

规则:佣金比例暂订为运单费用6%

8、运单取消 修改合约状态已取消

规则:运单仅在接单前可以取消

流程如下
钱包合约地址:0x7f2be7d3f2e672908c1c069ba7b993b6c9d53d93

运单合约地址:0xadd3a59e9f17115fe83633c3f23b37bd0652067f

货主1发起运单合约

货主1创建运单

  • 先给货主1充值200元 --》剩余400元

  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 运单id 1001 价格 150

平台审核运单

  • 0xcf33a0eef94c244b2d926d3dbba2666b51332b75 审核运单1001

货主给运单调价:

  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 运单1001 先涨价 10元 运单价格160
  • 0xfd917601078bb946bacba9a46f66012b9b4a7321 运单1001 然后降价 10元 --》平台需要重新去审核,运单价格150
  • 采用了简单的做法:降价后直接修改原始订单价格
  • 平台审核运单 0xcf33a0eef94c244b2d926d3dbba2666b51332b75 审核运单1001 --》成功

司机1接单:

  • 司机1 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2 接单 1001 --》如果VC证书已过期,报错
  • 在这里插入图片描述

司机1将运单转交给司机2

  • 司机1 0x0cfd803ea8323207e02479bdf8c64a20eab48ae2–》司机2 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1 运单1001

司机2 配送运单,运单已送达

  • 司机2 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1

货主1 将审核运单,状态变为已完成 0xfd917601078bb946bacba9a46f66012b9b4a7321 货主1

  • 司机2 0x717f63ae3f1a1169526ba70b7e3786bd4f0ce5b1 收到订单费用: 150 X 0.94 = 141元
  • 平台 0xcf33a0eef94c244b2d926d3dbba2666b51332b75 抽佣: 150 X 0.06 = 9元

业务系统:可以提供如下功能

1、查看系统各角色充值、转账、提现各类流水信息

2、统计货主或司机参与项目的数量,并查看花费的数字资产总数,每次项目参与的详情信息

3、查看各个业务方账户余额

开发时间:开始撰写技术方案及开发:0926 结束开发:1107 系统演示:1107+1108

效果:提供rest接口并演示整体功能 (不提供页面)

5、Solidity源码

// DID合约
pragma solidity ^0.8.0;contract Did {mapping(address => address) public owners;mapping(address => mapping(bytes32 => mapping(address => uint))) public delegates;mapping(address => uint) public changed;mapping(address => uint) public nonce;modifier onlyOwner(address identity, address actor) {require (actor == identityOwner(identity), "bad_actor");_;}event DIDOwnerChanged(address indexed identity,address owner,uint previousChange);event DIDDelegateChanged(address indexed identity,bytes32 delegateType,address delegate,uint validTo,uint previousChange);event DIDAttributeChanged(address indexed identity,bytes32 name,bytes value,uint validTo,uint previousChange);function identityOwner(address identity) public view returns(address) {address owner = owners[identity];if (owner != address(0x00)) {return owner;}return identity;}function checkSignature(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 hash) internal returns(address) {address signer = ecrecover(hash, sigV, sigR, sigS);require(signer == identityOwner(identity), "bad_signature");nonce[signer]++;return signer;}function validDelegate(address identity, bytes32 delegateType, address delegate) public view returns(bool) {uint validity = delegates[identity][keccak256(abi.encode(delegateType))][delegate];return (validity > block.timestamp);}function changeOwner(address identity, address actor, address newOwner) internal onlyOwner(identity, actor) {owners[identity] = newOwner;emit DIDOwnerChanged(identity, newOwner, changed[identity]);changed[identity] = block.number;}function changeOwner(address identity, address newOwner) public {changeOwner(identity, msg.sender, newOwner);}function changeOwnerSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, address newOwner) public {bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "changeOwner", newOwner));changeOwner(identity, checkSignature(identity, sigV, sigR, sigS, hash), newOwner);}function addDelegate(address identity, address actor, bytes32 delegateType, address delegate, uint validity) internal onlyOwner(identity, actor) {delegates[identity][keccak256(abi.encode(delegateType))][delegate] = block.timestamp + validity;emit DIDDelegateChanged(identity, delegateType, delegate, block.timestamp + validity, changed[identity]);changed[identity] = block.number;}function addDelegate(address identity, bytes32 delegateType, address delegate, uint validity) public {addDelegate(identity, msg.sender, delegateType, delegate, validity);}function addDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate, uint validity) public {bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "addDelegate", delegateType, delegate, validity));addDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate, validity);}function revokeDelegate(address identity, address actor, bytes32 delegateType, address delegate) internal onlyOwner(identity, actor) {delegates[identity][keccak256(abi.encode(delegateType))][delegate] = block.timestamp;emit DIDDelegateChanged(identity, delegateType, delegate, block.timestamp, changed[identity]);changed[identity] = block.number;}function revokeDelegate(address identity, bytes32 delegateType, address delegate) public {revokeDelegate(identity, msg.sender, delegateType, delegate);}function revokeDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate) public {bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "revokeDelegate", delegateType, delegate));revokeDelegate(identity, checkSignature(identity, sigV, sigR, sigS, hash), delegateType, delegate);}function setAttribute(address identity, address actor, bytes32 name, bytes memory value, uint validity ) internal onlyOwner(identity, actor) {emit DIDAttributeChanged(identity, name, value, block.timestamp + validity, changed[identity]);changed[identity] = block.number;}function setAttribute(address identity, bytes32 name, bytes memory value, uint validity) public {setAttribute(identity, msg.sender, name, value, validity);}function setAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes memory value, uint validity) public {bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "setAttribute", name, value, validity));setAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value, validity);}function revokeAttribute(address identity, address actor, bytes32 name, bytes memory value ) internal onlyOwner(identity, actor) {emit DIDAttributeChanged(identity, name, value, 0, changed[identity]);changed[identity] = block.number;}function revokeAttribute(address identity, bytes32 name, bytes memory value) public {revokeAttribute(identity, msg.sender, name, value);}function revokeAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes memory value) public {bytes32 hash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, nonce[identityOwner(identity)], identity, "revokeAttribute", name, value));revokeAttribute(identity, checkSignature(identity, sigV, sigR, sigS, hash), name, value);}}
// 钱包合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;import "./Did.sol";// 包含了角色管理、余额管理和基于角色的权限控制逻辑
contract MultiPartyWallet {uint256 DRIVER_ROLE = 1;  // 司机uint256 OWNER_ROLE = 2;   // 货主uint256 PLATFORM_ROLE = 3; // 平台uint256 REGULATOR_ROLE = 4; // 监管LingShuDid public lingShuDid; // DID合约// 存储每个地址的余额mapping(address => uint256) private balances;// 记录每个地址的角色mapping(address => uint256) private roles;// 事件声明event Create(address indexed from, uint256 _value);event Recharge(address indexed sender, uint256 amount);event Withdraw(address indexed receiver, uint256 amount);event Transfer(address indexed from, address indexed to, uint256 amount);// 修饰符,限制函数只能由特定角色调用modifier onlyRole(uint256 role) {require(roles[msg.sender] == role, "role not authorized");_;}constructor(LingShuDid _lingShuDid) {lingShuDid = _lingShuDid;}// 创建钱包 初始值为0function create() public {require(balances[msg.sender] == 0, "Balance created");balances[msg.sender] = 0;emit Create(msg.sender, balances[msg.sender]);}// 充值函数,目前仅货主允许充值function recharge(uint256 amount) public onlyRole(OWNER_ROLE) {require(amount > 0, "Amount should be greater than 0");balances[msg.sender] += amount; // balances[msg.sender] += _amount;emit Recharge(msg.sender, amount);}// 提现函数,不同角色有不同的权限function withdraw(uint256 amount) public {require(amount > 0, "Amount should be greater than 0");require(balances[msg.sender] >= amount, "Insufficient balance");if (roles[msg.sender] == REGULATOR_ROLE) {revert("Regulators cannot withdraw");}balances[msg.sender] -= amount;emit Withdraw(msg.sender, amount);}// 转账函数,不同角色有不同的权限,仅外部方法才能调用function transfer(address to, uint256 amount) external {require(amount > 0, "Amount should be greater than 0");require(balances[tx.origin] > amount, "Insufficient balance");// 司机和监管方不让转账if (roles[tx.origin] == DRIVER_ROLE) {revert("Drivers cannot transfer");} else if (roles[tx.origin] == REGULATOR_ROLE) {revert("Regulators cannot transfer");}balances[tx.origin] -= amount;balances[to] += amount;emit Transfer(tx.origin, to, amount);}// 查看余额函数function getBalance() external view returns (uint256) {return balances[tx.origin];}// 注册角色 为特定地址分配角色// 使用 VerifiableCredentials 合约来验证一个地址是否有权获得特定角色。   participant 角色地址// 授权4个地址 1、司机  2、货主 3、平台  4、监管function registerRole(address participant, uint256 role, uint256 roleInt) external {// 如果验证通过,分配角色roles[participant] = role;}
}
// 运单合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;import "./MultiPartyWallet.sol";contract OrderContract {// 定义运单状态enum OrderStatus {Created,   // 已创建Published, // 已发布Accepted,  // 已接单Delivered, // 已送达Completed, // 已完成Cancelled  // 已取消}// 运单审核状态enum OrderAuditStatus {wait,   // 待审核finish // 已审核}// 定义运单结构体struct FreightOrder {address consigner; // 货主地址address carrier; // 承运人地址(司机/车队)address platform; // 货运平台地址uint256 price; // 运单价格OrderStatus status; // 运单状态OrderAuditStatus auditStatus; // 审核状态}address private owner;MultiPartyWallet public multiPartyWallet;constructor(MultiPartyWallet _multiPartyWallet) {owner = msg.sender;multiPartyWallet = _multiPartyWallet;}// 存储所有运单mapping(uint256 => FreightOrder) public orders;// 记录司机当前是否有在途运单(目前限制每个司机只能在途一笔订单)mapping(address => bool) public driverInTransit;// 事件声明event OrderCreated(uint256 indexed orderId, address consigner);event OrderPublished(uint256 indexed orderId, address platform);event OrderAccepted(uint256 indexed orderId, address carrier);event OrderDelivered(uint256 indexed orderId);event OrderCompleted(uint256 indexed orderId);event OrderCancelled(uint256 indexed orderId);// 修饰符,限制函数只能由货主调用modifier onlyConsigner(uint256 orderId) {require(orders[orderId].consigner == msg.sender, "role not authorized");_;}// 修饰符,限制函数只能由货运平台调用modifier onlyPlatform(uint256 orderId) {require(orders[orderId].platform == msg.sender, "role not authorized");_;}// 修饰符,限制函数只能由承运人调用modifier onlyCarrier(uint256 orderId) {require(orders[orderId].carrier == msg.sender, "role not authorized");_;}// 修饰符,限制函数只能由特定订单状态调用modifier atStatus(uint256 orderId, OrderStatus status) {require(orders[orderId].status == status, "Order is not at the required status");_;}// 1. 创建运单function createOrder(uint256 orderId, uint256 price, address platformAddr) public  {require(orders[orderId].consigner == address(0), "order already exists");orders[orderId] = FreightOrder({consigner: msg.sender,carrier: address(0),platform: platformAddr,price: price,status: OrderStatus.Created,auditStatus: OrderAuditStatus.wait});emit OrderCreated(orderId, msg.sender);}// 2. 审核运单function auditOrder(uint256 orderId) public onlyPlatform(orderId) {require(orders[orderId].consigner != address(0), "Order does not exist"); // 检查运单是否存在require(orders[orderId].status == OrderStatus.Created || orders[orderId].status == OrderStatus.Published, "Invalid order status"); // 检查运单状态orders[orderId].status = OrderStatus.Published;orders[orderId].auditStatus = OrderAuditStatus.finish;emit OrderPublished(orderId, msg.sender);}// 3. 运单已接单function acceptOrder(uint256 orderId) public {require(orders[orderId].status == OrderStatus.Published, "Invalid order status"); // 运单状态必须为已发布require(orders[orderId].auditStatus == OrderAuditStatus.finish, "wait audit order dont support delivering"); // 待审核无法接单require(!driverInTransit[msg.sender], "Driver already has an order in transit");orders[orderId].carrier = msg.sender;orders[orderId].status = OrderStatus.Accepted;driverInTransit[msg.sender] = true;emit OrderAccepted(orderId, msg.sender);}// 4. 运单调价function adjustOrderPrice(uint256 orderId, uint256 newPrice) public onlyConsigner(orderId) {require(orders[orderId].status == OrderStatus.Published || orders[orderId].status == OrderStatus.Created, "Invalid order status");if (newPrice < orders[orderId].price) {// 降价需要审核,此处简化逻辑orders[orderId].price = newPrice;orders[orderId].auditStatus = OrderAuditStatus.wait;} else {// 涨价无需审核orders[orderId].price = newPrice;}}// 5. 运单转单  需要判断审核状态function transferOrder(uint256 orderId, address newCarrier) public onlyCarrier(orderId) {require(orders[orderId].status == OrderStatus.Accepted, "Invalid order status");// 此处简化了车队内部承运人转让的逻辑orders[orderId].carrier = newCarrier;}// 6. 运单已送达 需要判断审核状态 补充逻辑,判断当前合约方法调用者是否为运单承运人function deliverOrder(uint256 orderId) public onlyCarrier(orderId) {require(orders[orderId].status == OrderStatus.Accepted, "Invalid order status");orders[orderId].status = OrderStatus.Delivered;emit OrderDelivered(orderId);}// 7. 货主审核通过 需要判断审核状态function completeOrder(uint256 orderId) public onlyConsigner(orderId) atStatus(orderId, OrderStatus.Delivered) {FreightOrder storage order = orders[orderId];uint256 commission = (order.price * 6) / 100; // 计算佣金uint256 paymentToCarrier = order.price - commission;// 货主支付multiPartyWallet.transfer(order.carrier, paymentToCarrier); // 支付给司机/车队长multiPartyWallet.transfer(order.platform, commission); // 支付佣金给货运平台order.status = OrderStatus.Completed;emit OrderCompleted(orderId);}// 8. 运单取消(只能在创建状态下取消)function cancelOrder(uint256 orderId) public onlyConsigner(orderId) atStatus(orderId, OrderStatus.Created) {orders[orderId].status = OrderStatus.Cancelled;emit OrderCancelled(orderId);}// 9. 查询订单详情function getOrderDetails(uint256 orderId) public view returns (FreightOrder memory) {require(orders[orderId].consigner != address(0), "Order does not exist");return orders[orderId];}
}

6、项目代码及测试用例

见这个项目https://gitee.com/qiwenjie1993/chain

相关文章:

区块链应用第1讲:基于区块链的智慧货运平台

基于区块链的智慧货运平台 网络货运平台已经比较成熟&#xff0c;提供了给货源方提供找司机的交易匹配方案&#xff1b;其中包含这几个角色&#xff1a;货主、承运人(司机、车队长)、监管机构、平台。司机要想接单&#xff0c;依赖于多个中心化的第三方平台&#xff0c;且三方平…...

量化交易系统开发-实时行情自动化交易-风险控制

19年创业做过一年的量化交易但没有成功&#xff0c;作为交易系统的开发人员积累了一些经验&#xff0c;最近想重新研究交易系统&#xff0c;一边整理一边写出来一些思考供大家参考&#xff0c;也希望跟做量化的朋友有更多的交流和合作。 接下来继续说说风险控制模块&#xff0…...

深入探索 Seaborn:高级绘图的艺术与实践

引言 在数据科学领域&#xff0c;数据可视化是至关重要的一步。它不仅能够帮助我们更好地理解数据&#xff0c;还能有效地传达信息&#xff0c;支持决策过程。Seaborn 是一个基于 Matplotlib 的高级 Python 数据可视化库&#xff0c;它提供了许多高级绘图功能&#xff0c;使得…...

《现代工业经济和信息化》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a; 问&#xff1a;《现代工业经济和信息化》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《现代工业经济和信息化》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;山西省工业和…...

【TS】九天学会TS语法——2.TypeScript基本类型及变量声明

今天学习的内容是TypeScript 基本类型&#xff0c;包括 number, string, boolean, any, void 等&#xff0c;以及变量声明的方式和区别。 基本类型介绍变量声明&#xff08;var, let, const&#xff09;类型注解 开始学习 目录 引言 一、基本类型介绍 二、变量声明 1.概念解析 …...

html+js+css实现拖拽式便签留言

前些日子在网上冲浪时&#xff0c;看到一个便签式留言墙&#xff0c;让人耳目一新。心想这个看着不错&#xff0c;额想要。于是便开始搜寻是否有相应开源插件&#xff0c;想将其引入自己的博客中。但是搜寻了一圈&#xff0c;都没有符合预期的,要么功能不符合。有的功能符合&am…...

Redis原理篇——Redis数据结构

Redis原理篇 1、原理篇-Redis数据结构 1.1 Redis数据结构-动态字符串 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存…...

pdf文件预览和导出

抢先观看&#xff1a; window.URL.createObjectURL()&#xff1a; 用于根据传入的 Blob 对象或 File 对象生成一个临时的、可访问的 URL,仅在浏览器会话中有效&#xff0c;并且不会上传到服务器。 const url window.URL.createObjectURL(blob);Blob 对象&#xff1a; 是 …...

服务器数据恢复—RAID5阵列硬盘坏道掉线导致存储不可用的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台EqualLogic存储中有一组由16块SAS硬盘组建的RAID5阵列。上层划分了4个卷&#xff0c;采用VMFS文件系统&#xff0c;存放虚拟机文件。 服务器存储故障&#xff1a; 存储RAID5阵列中磁盘出现故障&#xff0c;有2块硬盘对应的指示灯亮黄灯…...

快速傅里叶变换(FFT)基础(附python实现)

对于非专业人士&#xff0c;傅里叶变换一直是一个神秘的武器&#xff0c;它可以分析出不同频域的信息&#xff0c;从时域转换到频域&#xff0c;揭示了信号的频率成分&#xff0c;对于数字信号处理&#xff08;DSP&#xff09;、图像、语音等数据来说&#xff0c;傅里叶变换是最…...

使用Docker-compose安装mysql5.7

1.首先选择一个目录用来存放docker-compse文件以及mysql的数据&#xff08;例如logs、conf&#xff09; cd /home mkdir mysql vi docker-compose.yml2.填写docker-compse.yml内容 version : 3 services:mysql:# 容器名(以后的控制都通过这个)container_name: mysql# 重启策略…...

如何管理PHP的API部署环境

管理PHP的API部署环境是一个涉及多个步骤和考虑因素的过程。以下是一些关键步骤和最佳实践&#xff0c;用于管理PHP的API部署环境&#xff1a; 一、选择合适的服务器和配置环境 选择服务器&#xff1a;根据API的访问量和性能需求&#xff0c;选择合适的服务器。可以选择物理服…...

web——sqliabs靶场——第一关

今天开始搞这个靶场&#xff0c;从小白开始一点点学习,加油&#xff01;&#xff01;&#xff01;&#xff01; 1.搭建靶场 注意点&#xff1a;1.php的版本问题&#xff0c;要用老版本 2.小p要先改数据库的密码&#xff0c;否则一直显示链接不上数据库 2.第一道题&#xff0…...

tartanvo ubuntu 20.04部署

1. 所有环境安装流程参考 2. 运行python3 tartanvo_node.py出现问题&#xff1a; ImportError: cannot import name int from numpy版本问题&#xff0c;卸载当前版本并更换版本&#xff1a; pip uninstall numpy pip install numpy1.22.4问题解决。 3. 采用2to3脚本将其代…...

SpringBoot整合Freemarker(三)

定义循环输出的宏 <#macro list title items> ${title?cap_first}:<#list items as x>*${x?cap_first}</#list> </#macro><list items["mouse", "elephant", "python"] title"Animals"/> 输出结果…...

Android 一个APP打开另一个app的两种方式,需添加QUERY_ALL_PACKAGES权限

加&#xff1c;uses-permission android:name"android.permission.QUERY_ALL_PACKAGES"/> 方式1&#xff1a;打开外部app&#xff0c;在新窗口打开。 private void doStartAppPackageName(String packagename) { // 通过包名获取此APP详细信息&#x…...

<数据集>草莓叶片病害识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;4371张 标注数量(xml文件个数)&#xff1a;4371 标注数量(txt文件个数)&#xff1a;4371 标注类别数&#xff1a;7 标注类别名称&#xff1a;[Angular Leafspot, Anthracnose Fruit Rot, Blossom Blight, Gray Mol…...

React 中 `key` 属性的警告及其解决方案

React 中 key 属性的警告及其解决方案 文章目录 React 中 key 属性的警告及其解决方案1. 引言2. 什么是 key 属性3. key 属性的重要性4. 常见的 key 属性警告及其原因4.1 缺少 key 属性4.2 使用不稳定的 key&#xff08;如索引&#xff09;4.3 重复的 key 值 5. 如何解决 key 属…...

OpenHarmony4.1蓝牙芯片如何适配?触觉智能RK3568主板SBC3568演示

当打开蓝牙后没有反应时&#xff0c;需要排查蓝牙节点是否对应、固件是否加载成功&#xff0c;本文介绍开源鸿蒙OpenHarmony4.1系统下适配蓝牙的方法&#xff0c;触觉智能SBC3568主板演示 修改对应节点 开发板蓝牙硬件连接为UART1&#xff0c;修改对应的节点&#xff0c;路径为…...

濮良贵《机械设计》第十版课后习题答案全解PDF电子版

《机械设计》(第十版)是“十二五”普通高等教育本科国家级规划教材&#xff0c; 是在《机械设计》(第九版)的基础上修订而成的。本次修订主要做了以下几项工作&#xff1a; 1. 内容的适当更新——自本书第九版出版以来&#xff0c; 机械工程及相关领域的新理论、新技术和新标准…...

Python进阶语法探索:列表推导式

在Python编程中&#xff0c;列表推导式&#xff08;List Comprehensions&#xff09;是一种简洁而强大的语法结构&#xff0c;它允许你以一行代码的形式创建列表&#xff0c;同时执行循环、条件判断等操作。列表推导式不仅提高了代码的可读性&#xff0c;还显著提升了编程效率。…...

java合并图片与文字

通过java来绘制海报&#xff0c;加载外部字体并设置样式大小与加粗、设置背景图、合并图片&#xff0c;下面是示例 import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.…...

OpenCV快速入门

OpenCV&#xff08;Open Source Computer Vision Library&#xff0c;开源计算机视觉库&#xff09;是一个广泛应用于图像处理、计算机视觉、视频分析等领域的开源库。它不仅适用于研究人员和开发人员&#xff0c;还被广泛用于学术、工业和商业应用。本篇文章将帮助你快速了解 …...

ArcGIS软件之“计算面积几何”地图制作

一、消防站的泰森多边形 效果图&#xff1a; 二、人口调查的泰森多边形 确定后效果图&#xff1a; 三、人口调查的泰森多边形属性设置 确定后的效果图&#xff1a; 四、计算面积几何&#xff0c;用于求密度 先添加字段area_1&#xff0c;然后设置浮点型及字段属性 五…...

RHCE 第四次作业

一.搭建dns服务器能够对自定义的正向或者反向域完成数据解析查询。 1.配置环境 [rootlocalhost ~]# yum install bind [rootlocalhost ~]#systemctl stop firewalld [rootlocalhost ~]#setenforce 0 2.配置DNS主服务器 [rootlocalhost ~]# vim /etc/named.conf options { …...

【贪心算法】No.1---贪心算法(1)

文章目录 前言一、贪心算法&#xff1a;二、贪心算法示例&#xff1a;1.1 柠檬⽔找零1.2 将数组和减半的最少操作次数1.3 最⼤数1.4 摆动序列1.5 最⻓递增⼦序列1.6 递增的三元⼦序列 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到…...

分布式光伏管理办法

随着分布式光伏项目的不断增加&#xff0c;传统的管理方式已经难以满足高效、精准的管理需求。光伏业务管理系统作为一种集信息化、智能化于一体的管理工具&#xff0c;正在逐步成为分布式光伏项目管理的重要支撑。 光伏业务管理系统通过数字化手段实现对光伏业务全流程的精细化…...

2024最新软件测试面试热点问题

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 大厂面试热点问题 1、测试人员需要何时参加需求分析&#xff1f; 如果条件循序 原则上来说 是越早介入需求分析越好 因为测试人员对需求理解越深刻 对测试工…...

如何利用探商宝精准营销,抓住行业机遇——以AI技术与大数据推动企业信息精准筛选

近年来&#xff0c;随着人工智能与大数据技术的迅猛发展&#xff0c;企业的营销手段和策略发生了巨大变化。尤其是在信息爆炸的数字时代&#xff0c;如何有效利用这些技术在海量数据中精准找到潜在客户&#xff0c;已成为中小企业亟待解决的核心问题。 最近&#xff0c;全球人…...

嵌入式硬件电子电路设计(三)电源电路之负电源

引言&#xff1a;在对信号线性度放大要求非常高的应用需要使用双电源运放&#xff0c;比如高精度测量仪器、仪表等;那么就需要给双电源运放提供正负电源。 目录 负电源电路原理 负电源的作用 如何产生负电源 负电源能作功吗&#xff1f; 地的理解 负电压产生电路 BUCK电…...