区块链应用第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 一般为用户地址
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..TCYt5X
sITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUc
X16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtj
PAYuNzVBAh4vGHSrQyHUdBBPM"
}
}
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、流程图
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;
}
// 创建钱包 初始值为0
function 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