区块链应用第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..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、流程图

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;
    }

    // 创建钱包 初始值为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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/913700.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot+Vue实现留守儿童爱心网站

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

关于分治法左右区间单调遍历应该如何设计

阅读以下文章&#xff0c;首先至少要求通过一道分治法的题目或听过一道该类型的讲解。 对于分治的题目&#xff0c;想必你应该知道&#xff0c;通常我们是对于一个区间拆分两个部分&#xff0c;而最小子问题通常是只包含一个元素的区间数组。为了后续方便处理更大范围的区间&am…

友思特应用 | 动态捕捉:高光谱相机用于移动产线上的食品检测

导读 高光谱成像技术能够为食品安全助力。以友思特BlackIndustry SWIR 1.7 Max 为代表的高光谱相机&#xff0c;完美解决了移动产线检测的应用难点。 高光谱技术&#xff1a;为食品安全保驾护航 食品安全一直是大众关心的热点话题&#xff0c;提供安全、高质量的食品需要对食…

【论文阅读】医学SAM适配器:适应医学图像分割的任意分割模型

【论文阅读】医学SAM适配器&#xff1a;适应医学图像分割的任意分割模型 文章目录 【论文阅读】医学SAM适配器&#xff1a;适应医学图像分割的任意分割模型一、介绍二、联系工作三、方法四、实验 Medical SAM Adapter: Adapting Segment Anything Model for Medical Image Segm…

数据结构 C/C++(实验一:线性表)

&#xff08;大家好&#xff0c;今天分享的是数据结构的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 提要&#xff1a;实验题目 一、实验目的 二、实验内容及要求 三、算法思想 实验1 实验2 四、源程序及注释 …

Oracle 23AI创建示例库

一、示例库介绍 多年来&#xff0c;Oracle 一直使用简单的数据库模式 SCOTT 及其两个突出的表 EMP 和 DEPT&#xff0c;用于文档和培训中的各种示例。但不少小伙伴并不知道如何创建这些示例数据&#xff0c;其实Oracle官方上就有提供对应的方法&#xff0c;本文就带领大家完成…

uniapp组件实现省市区三级联动选择

1.导入插件 先将uni-data-picker组件导入我们的HBuilder项目中&#xff0c;在DCloud插件市场搜索uni-data-picker 点击下载插件并导入到我们的项目中 2.组件调用 curLocation &#xff1a;获取到的当前位置&#xff08;省市区&#xff09; <uni-data-picker v-slot:defa…

九州未来再度入选2024边缘计算TOP100

随着数智化转型的浪潮不断高涨&#xff0c;边缘计算作为推动各行业智能化升级的重要基石&#xff0c;正在成为支持万物智能化的关键点。近日&#xff0c;德本咨询(DBC)联合《互联网周刊》(CIW)与中国社会科学院信息化研究中心(CIS)&#xff0c;共同发布《2024边缘计算TOP100》榜…

Linux指令的基本使用

Linux 的基本使用 一. Linux 的常用指令 1. ls 语法: ls [选项] [目录或文件] 功能: 对于目录&#xff0c;该命令列出该目录下的所有子目录与文件. 对于文件&#xff0c;将列出文件名以及其他信息 常用选项: -a 列出目录下的所有文件, 包括以 . 开头的隐含文件.-d 将目录像…

linux-c 使用c语言操作sqlite3数据库-1

一、练习目标 1、目标 1、使用sqlite3_exec执行查询语句&#xff0c;并将查询结果insert到链表中&#xff0c;最后打印链表的内容&#xff1b; 2、使用sqlite3_get_table执行查询语句&#xff0c;并以key&#xff1a;value的方式&#xff0c;打印查询结果。 2、环境准备 2.1、…

11个简单易用的电商购物车设计案例

文章目录 前言正文1.扁平化设计购物车2.无表格布局购物车3.美食购物车4.响应式购物车5.jQuery购物车6.动态价格更新购物车7.标签式滑动购物车8.动态商店与购物车一体化设计9.简约清爽的购物车设计10.基于Vue.js的购物车11.域名购物车 总结 前言 现在的电子商务网站&#xff0c…

AI绘画到底怎么画,才能出好图!一文详解

前言 在当今数字化的时代&#xff0c;AI 绘画以其强大的创造力和便捷性&#xff0c;成为了众多艺术爱好者和创作者的新宠。无论是专业画家想要拓展创作思路&#xff0c;还是业余爱好者渴望展现自己的创意&#xff0c;AI 绘画都提供了无限的可能。那么&#xff0c;究竟如何才能…

计算机组成原理——计算机的基本组成

1.计算机硬件的基本组成 冯-诺依曼计算机的特点&#xff1a; 1.计算机由运算器&#xff08;ALU&#xff09;、控制器&#xff08;CU&#xff09;、存储器、输入设备、输出设备五个部分组成 2.指令和数据以同等地位存于存储器中&#xff0c;可以通过地址访问 3.指令和数据以二…

【人工智能】ChatGPT多模型感知态识别

目录 ChatGPT辅助细化知识增强&#xff01;一、研究背景二、模型结构和代码任务流程一&#xff1a;启发式生成 三、数据集介绍三、性能展示实现过程运行过程训练过程 ChatGPT辅助细化知识增强&#xff01; 多模态命名实体识别&#xff08;MNER&#xff09;最近引起了广泛关注。…

python 爱心邮件代码

import smtplib import time from email.mime.text import MIMEText import requests from lxml import etree import datetime from requests.exceptions import RequestException# 邮件配置 sender_maile # 发件人地址 sender_pass # 邮件授权码 boy_name # 发件人姓…

ssm+jsp704学术团队管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…

贪心算法-汽车加油

这道题目描述了一个汽车旅行场景&#xff0c;需要设计一个有效的算法来决定在哪几个加油站停车加油&#xff0c;以便最小化加油次数。题目给出了汽车加满油后的行驶距离n公里&#xff0c;以及沿途若干个加油站的位置。我们需要找出一个方案&#xff0c;使得汽车能够完成整个旅程…

[Docker#2] 发展历史 | Namespace环境隔离 | Cgroup资源控制

目录 1.发展历史 Jail 时代 云时代 云原生时代 技术标准的确立 虚拟机 vs Docker 2. 容器化技术 2.1 Namespace 命令详解 1. dd 命令 2. mkfs 命令 3. df 命令 4. mount 命令 5. unshare 命令 实战 进程隔离 文件隔离 2.2 CGroup 相关命令 2.1 pidstat 2.…

【Ubuntu学习】Ubuntu无法使用vim命令编辑

问题 在VMware首次安装Ubuntu&#xff0c;使用vi指令对文件进行编辑&#xff0c;按i键后无法更改文件内容。 原因 由于Ubuntu中预装的是vim-tiny&#xff0c;平时开发中需要使用vim-full。 解决方案 卸载预装vim sudo apt-get remove vim-common安装vim-full sudo apt-get …

同轴全息图和离轴全息图

一、同轴全息图 1.1 记录 设透明的物体(相位物)的振幅透过率为: t0是一个很高的平均透射率,表示围绕平均值的变化。 透射光场可以看成由两项组成: 一项是由t0表示的强而均匀的平面波, 它相当于波前记录时的参考波, 另一 项是Δt 所代表的弱散射波, 它相当于波前记录时的物光波…