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

项目底链华为链切换长安链经验总结

项目底链华为链切换长安链经验总结

  • 前言
  • 业务需求分析
  • 智能合约重写
    • k-v存储结构设计
    • 设计上链存储的结构体
    • 使用迭代器查询历史记录
    • 长安链合约编辑器历史记录返回错误
    • 材料上链非必传字段 Int 类型自动赋值
    • 长安链cmc工具部署合约ca证书需齐全
    • 分页查询截取处理,返回 nil处理
    • 迭代器前缀匹配因ascii 截取有遗漏
    • 历史查询反序列化失败错误问题
  • 迁链sdk工具开发
    • 1.java.lamg:RuntimeException: chainClient error:no node can use
    • 2.not found GLIBC_2.25
    • 3.java. io. FileNotFoundException: log/log/audit sdk 0.log. (No such file or directory)
    • 4.java.lang. ClassCastException: org.bouncycastle.jce.provider.BouncyCastleProvider cannot be cast to org.bouncycastle.jce.provider. BouncyCastleProvider
    • 5.UnsatisfiedLinkError: C:\Users\dsjadmin\AppData\Local\Temp\libcrypto-1_1-x64.dll: Can't find dependent libraries
  • 压测

前言

最近工作遇到的一个新项目需求,某市大数据中心因某些原因需要将原有业务链的底链(华为链)切换为长安链,其中提出的需求是,原有的业务使用方(其他区级节点以及相关使用的业务系统)要无感切换,将原业务链的历史数据迁移到新链上且保留华为链相关属性标识(区块高度、区块哈希、区块时间戳、交易哈希),改造新的底链交互的sdk和智能合约重写等一系列要求,其中设计到多方公司交互和多区同步协助配合测试新 sdk 等,以及因政务敏感数据,办公地点也得限制才能访问内网,项目上也遇到一系列问题,就记录一下做一个阶段性记录吧。

业务需求分析

因为该项目涉及到对方参与,新底链厂商呢又涉及到多方利益牵扯分配,加入了新的厂商,于是就有了数据稽核厂商a,新底链厂商b,签名验签厂商c,对于多方协同也是我此次经验收货吧,还有项目总集厂商d和政务代表e,总之我们就是万能转换器,需要多个对接口对接不同需要的人和事。好了总列一下客户需求吧

  1. 智能合约重构
  2. 业务链sdk研制:新下发的 sdk 与原有的 sdk 接口参数入参出参保持一致
  3. 历史业务数据迁移保留原华为链的区块高度、区块哈希、区块时间戳、交易哈希
  4. 部署节点数据18个,且一个市节点有额外操作权限,其他16区节点和1个社会化节点不同区别
  5. 需要无感升级,无缝切换
  6. 因业务体量估计,当前快高60万左右,预估今年上链1.5亿索引总量,对上链性能领导也有一定要求,也是后续折磨点
    在这里插入图片描述

智能合约重写

从上述简单描述可知,涉及到底链切换,原华为链的智能合约人家不给,只能根据业务重新分析从头开始写,这也是我工作任务的大头,梳理业务逻辑,重写智能合约就是第一部分了,涉及保密就不贴完整的智能合约了,这里罗列几条我写的时候碰到的问题以及解决方式吧。
因上链内容大部分都是材料相关信息,材料原始文件不上链,将其索引相关信息上链,就得考虑一对多的情况。以及要满足其中一两个接口的复杂检索需求,这块因底链切换改造最为头痛。因长安链对go版本的智能合约兼容性最佳,于是使用go写该次项目的智能合约。

k-v存储结构设计

因为链上存储格式的特殊性,与传统数据库不同,针对该项项目索引存储结构的设计最为重要,确定好后续检索和唯一性存储的 key,以及对应 value 的设计是最为重要的第一步。
本次根据业务需求设计相关k-v 存储,因是材料相关存在多层级和同一份材料对应多个文件,于是用字节数组存储 value相关,且调用一次上链存储5次对应的 k-v 结构,保障某环节出错则该次上链失败,避免链上无法回滚等问题。
简单罗列该业务的 k-v 结构
材料信息表相关

序号索引前缀keyvalue
1OwnerHashIndexKey所属着哈希值+材料唯一标识材料唯一标识
2MaterialHashIndexKey文件哈希值材料唯一标识
3MaterialIndexKey材料唯一标识材料索引信息
4LibraryBasicKey材料唯一标识材料基本项信息

复用信息表相关

序号索引前缀keyvalue
1MaterialIndexKey复用信息唯一标识复用基本项信息
2ReuseInfoIndexKey材料唯一标识复用信息唯一标识

1、其中索引前缀是为了快速检索,与不同 k-v 做区分,存储的 key 由索引前缀+key 构成,value 存储相关值
2、其中材料信息中的第一条中 key 由(所属着哈希值+材料唯一标识)组合构成,是为了后续业务需要满足检索同一所属者名下的所有材料而设计的组合索引构成方式,便于通过前缀匹配所属着哈希值找到同一人下的所有材料。

设计上链存储的结构体

这块主要是根据业务逻辑梳理上链存储结构体,且按照底链迁移需求,需要保留原始历史数据中携带华为链标识信息(区块高度、区块哈希、区块时间戳、交易哈希),且保证和原来历史信息存储结构一致,不会出现历史数据和新上链后续解析结构体不一致的情况。
在智能合约设计上,合约调用方法上特意预留了两个历史数据迁移使用的信息上链方法(材料索引信息上链(迁移)、电子材料复用记录(迁移)),虽然存储结构体都是一致的改造后,但只有历史数据迁移的时候会调用这两个上链方法且带有原有链标识信息,其余新上链的信息都是走新合约方法上链,且那几个标识信息都是非必填。

使用迭代器查询历史记录

因业务需要查询某个具体电子材料的历史上链记录,使用迭代器查询历史,再遍历迭代器查询返回结果取所需返回值。

	historyKvIterator, err := sdk.Instance.NewHistoryKvIterForKey(LibraryBasicKey, materialUid)

长安链合约编辑器历史记录返回错误

  • 问题
    使用长安链的合约编辑器。查某个材料历史记录,返回的时间戳显示有问题,查某个材料历史记录,返回的时间戳显示有问题,显示为2021-12-12 12:12:12, tx_id 都一样为a8e85cca20684386b8da79137d7d6d519cce98a65a4446a788aad3b779da974a
    长安链合约编辑器,使用小 tips:注意右上角版本切换
  • 解决
    该问题为长安链的合约编辑器默认赋予的时间和 tx_id,历史查询都显示为同一个,在实际测试环境使用迭代器查询返回后一切正常

材料上链非必传字段 Int 类型自动赋值

因go语言特性,结构体为int类型的字段非必填未传值的时候会默认给赋值为0,于是链上存储的时候可能就会给没有上传值的字段赋值为0,这块要确保与实际业务不冲突

长安链cmc工具部署合约ca证书需齐全

因刚开始接触项目时信息不全,说是需要通过cmc自行部署合约,于是根据长安链官网说明部署测试了一下,发现在部署合约时候需要节点的 ca证书齐全才行,必须俩文件都有才能部署成功,以下是我自己测试时候使用到的一些命令,简单记录一下,官网部署文档参考长安链cmc工具

  • 部署合约
./cmc client contract user create \
--contract-name=go_cmc_test \
--runtime-type=DOCKER_GO \
--byte-code-path=./testdata/claim-docker-go-demo/docker-fact.7z \
--version=1.0 \
--sdk-conf-path=./testdata/sdk_config.yml \
--admin-key-file-paths=/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg10-cmtestnode10/user/cmtestuser10.sign.key,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg11-cmtestnode11/user/cmtestuser11.sign.key,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg12-cmtestnode12/user/cmtestuser12.sign.key,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg13-cmtestnode13/user/cmtestuser13.sign.key \
--admin-crt-file-paths=/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg10-cmtestnode10/user/cmtestuser10.sign.crt,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg11-cmtestnode11/user/cmtestuser11.sign.crt,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg12-cmtestnode12/user/cmtestuser12.sign.crt,/wondersgroup/chainmaker/chainmaker-go/tools/cmc/testdata/crypto-config/TestCMorg13-cmtestnode13/user/cmtestuser13.sign.crt \
--sync-result=true \
--params="{}"
  • 调用合约
./cmc client contract user invoke \
--contract-name=go_cmc_test \
--method=save \
--sdk-conf-path=./testdata/sdk_config.yml \
--params="{\"file_hash\":\"ab3456df5799b87c77e7f88\",\"file_name\":\"name007\",\"time\":\"6543234\"}" \
--sync-result=true
  • 查询合约
./cmc client contract user get \
--contract-name=go_cmc_test \
--method=findByFileHash \
--sdk-conf-path=./testdata/sdk_config.yml \
--params="{\"file_hash\":\"ab3456df5799b87c77e7f88\"}"
  • 合约升级
./cmc client contract user upgrade \
--contract-name=material_contract_test \
--runtime-type=DOCKER_GO \
--byte-code-path=./testdata/claim-docker-go-demo/material_contract_test.7z \
--version=22.0 \
--sdk-conf-path=./testdata/sdk_config.yml \
--admin-key-file-paths=./testdata/crypto-config/TestCMorg10-cmtestnode10/user/cmtestuser10.sign.key,./testdata/crypto-config/TestCMorg11-cmtestnode11/user/cmtestuser11.sign.key,./testdata/crypto-config/TestCMorg12-cmtestnode12/user/cmtestuser12.sign.key,./testdata/crypto-config/TestCMorg13-cmtestnode13/user/cmtestuser13.sign.key \
--admin-crt-file-paths=./testdata/crypto-config/TestCMorg10-cmtestnode10/user/cmtestuser10.sign.crt,./testdata/crypto-config/TestCMorg11-cmtestnode11/user/cmtestuser11.sign.crt,./testdata/crypto-config/TestCMorg12-cmtestnode12/user/cmtestuser12.sign.crt,./testdata/crypto-config/TestCMorg13-cmtestnode13/user/cmtestuser13.sign.crt \
--sync-result=true \
--params="{}"

分页查询截取处理,返回 nil处理

因查询符合条件的数据返回时,在处理分页最后一页数据截取时,获取从起始点到末位所有数据的切片时不能写末位置,因为Go 语言中,对切片进行分页时,如果使用了[m:n]这样的语法表示从索引m到n(不包括n)的切片,如果m和n相等,则表示一个空切片。
下面是一个简单的例子,演示了当使用[m:n],其中m==n时会得到一个nil切片的情况:

package main
import "fmt"
 
func main() {
    slice := []int{1, 2, 3, 4, 5}
    page := slice[2:2] // 当m==n时,page将是nil切片
 
    if page == nil {
        fmt.Println("Page is nil")
    } else {
        fmt.Println("Page is not nil")
    }
 
    fmt.Println("Length of page:", len(page)) // 输出切片长度
}

结果输出为
Page is nil
Length of page: 0

切片截取到最后一条处理[2:] .不能写成[2:2]会出问题,超出切片大小,分页出错报 nil 错误

迭代器前缀匹配因ascii 截取有遗漏

因查询某个所属人名下的材料,由所属着哈希值+材料唯一标识构成索引存储的 key,因属着哈希值一致,就需要找到所有匹配所属着哈希值的电子材料,因此使用迭代器搜寻,之前使用的方法是NewIteratorWithField,为此还需计算出开始值和结束值,结果某次查询的时候发现查出来的材料和预期有偏差,才发现因为这个方式是根据ascii值前缀匹配查询造成的遗漏问题,后续再查询迭代器的其他方法,找到了更好的方式,使用NewIteratorPrefixWithKeyField替代。
在这里插入图片描述
两种方式的具体使用方式这里就放图展示更明显
在这里插入图片描述
NewIteratorPrefixWithKeyField只需两个参数,key 和需要匹配的 field(这里指我们的所属着哈希)完美满足我们业务检索需求,且不会出现前缀匹配遗漏的问题
附原文解释智能合约开发

历史查询反序列化失败错误问题

  • 遇见的问题一:在查询某材料的链上存储历史记录时候返回报错:go String2Poly failed, err=illegal base64 data at input byte 1508
    解决方法:查历史信息接口,返回[]byte 反序列化[]libriyinfo 会提示转义错误,用 interface{}接受转义内容,因为存储的电子材料结构体本身嵌套较为复杂,有多层[],层层嵌套,可能转义的时候base64转对象为[]byte有问题,就用interface{}兼容性更强,就能避免报这个错误
  • 遇见的问题二:由interface{}修改后基本没出现同意的错误,但是偶然一次查询历史又出现反序列化失败错误问题
    解决方案:因为有删除历史,查询的该条电子材料历史记录中有一条删除结构体为 null,所以反序列化失败,后续根据业务调整返回判断是否需要序列化删除信息,或者在查询历史信息时先查询该条材料是否被删除过,比较在该项目业务中,删除电子材料的操作权限可操作的人和可能较少,数量级也相较其他较少。

迁链sdk工具开发

合约改造好后就涉及到新旧sdk替换的问题,因各节点上链通过原java sdk上到旧底链,现在要求出入参不变无感知切换底链是工作一,迁移旧链数据上到新链是工作二,都由这个迁链 sdk 工具完成,这部分就涉及多方沟通协调问题,也是最麻烦最繁琐的,涉及到迁链 sdk 在下发到各区节点时,各区适配和应用的问题,期间遇到的各类问题也总结了文档记录如下

1.java.lamg:RuntimeException: chainClient error:no node can use

可能原因:
1、证书或者链配置有问题
2、链异常,如节点不可用或者链服务器资源耗光了
解决方法:
联系sdk提供方排查问题–这块旧可能涉及到配置相关问题了,得检查下发的 sdk 相关配置里是否符合该节点等

2.not found GLIBC_2.25

服务器GLIBC版本过低
注意:需要查看的是服务运行的环境,如果使用的容器部署项目需要确定容器环境的GLIBC版本(ps:因各节点的部署环境和对接人员技术熟练度不同,真实碰到利用容器部署的运行环境过低问题)

  • 使用命令查看机器GLIBC版本:
strings /lib64/libc.so.6 | grep GLIBC

升级GLIBC版本:

参考:centos7升级glibc库到glibc-2.28

3.java. io. FileNotFoundException: log/log/audit sdk 0.log. (No such file or directory)

稽核日志目录无法自动创建(目前只发现在容器部署的时候会发生这种情况)
解决方法:
联系sdk提供方,提供部署机器确定存在的绝对路径目录,由sdk提供方修改日志path(ps:这块也是属于因地制宜踩坑实录了吧,只能针对特殊问题单独解决)

4.java.lang. ClassCastException: org.bouncycastle.jce.provider.BouncyCastleProvider cannot be cast to org.bouncycastle.jce.provider. BouncyCastleProvider

项目启动环境包冲突(目前发现tongweb7.0.4.9_M4启动服务的时候会出现)
解决方法:
升级tongweb版本为7.0.4.2(ps:政府相关机构的服务器环境挺多都比较滞后,但因为区块链技术新颖性涉及到其他方使用到的包环境都较新所以会出现该类问题)

5.UnsatisfiedLinkError: C:\Users\dsjadmin\AppData\Local\Temp\libcrypto-1_1-x64.dll: Can’t find dependent libraries

windows系统下找不到libraries
解决方法:
将sdk包下win32-x86-64目录拷贝到项目的resources目录下

压测

当完成好上述工作就需要对这个迁链的sdk进行测试了,功能测试基本没问题,有问题也在上述问题汇总中解决了,最麻烦的就是性能测试了,因该项目预期数据总量会上1.5亿条索引数据,所以领导对于这个上链性能也有一定要求,在几轮反复压测磨合中,调试到最好的情况,其中也暴露了一些问题。
在几次压测中为调试最佳上链tps,控制了几个变量点

  • 第一:调整共识节点数量,由18个共识节点转变为4共识节点14同步节点
  • 第二:压测源 sdk服务个数,由4个更改为16个,增加客户端数量
  • 第三:测试不同并发量,分别有200,400,600,800,1200,3000等
  • 第四:其中对接方之一更改过一次大版本,由原来的 V1.0版本,更改为 V2.0版本(ps: 这也是压测后期不达标的隐患之一,因和另外底链第三方厂商他们版本没能互相适配导致错过第一次上线计划)
  • 第五:调整了一个底链配置参数enable_tx_resu
    lt_dispatcher,设置为 true,默认为 false(ps:后续也因为找到了这个参数调整最终符合领导要求的性能指标)

压测就是一个不断调试优化,尝试各种可能的漫长过程,特别是这种涉及到多个参与方协助的,虽然几经波折但是最终还是调试到领导满意的性能要求,写链性能在最复杂的业务接口也能满足1300+TPS,平均响应时间391ms,查询接口读链单节点为6300+QPS,评价响应时间123ms,总共18个节点理论值可达110000+QPS((注:链共18个节点,理论上总QPS=单节点QPS*18)),最复杂的分页查询接口单节点为3400+QPS也能满足需求。至于其他的稳定性测试和衰减性测试也做了相关测试符合要求。
在这里插入图片描述


http://www.kler.cn/a/448925.html

相关文章:

  • MyBatis通过注解配置执行SQL语句原理源码分析
  • Deformable DETR:Deformable Transformers for End-to-End Object Detection论文学习
  • vulnhub靶场【WhowWantsToBeKing】之1
  • Aec-Library-Website 项目常见问题解决方案
  • iOS从Matter的设备认证证书中获取VID和PID
  • C++中的模板元编程
  • 简易CPU设计入门:本系统中的通用寄存器(一)
  • 刷题 两数之和
  • Aec-Library-Website 项目常见问题解决方案
  • laya游戏引擎中打包之后图片模糊
  • 【python高级】341-计算机网络基础 for Socket网络编程
  • VSCode:IDE显示设置 --自定义字体及主题颜色
  • 【JVM】如何有效调整JVM年轻代和老年代的大小
  • Java项目--仿RabbitMQ的消息队列--基于MQ的生产者消费者模型
  • elasticache备份
  • Debian 12.0 上为 Nginx 配置 SSL/TLS 证书
  • Vue:父页面调用子页面方法等待完成
  • Zabbix告警通知部署方案详解
  • ELM分类-单隐藏层前馈神经网络(Single Hidden Layer Feedforward Neural Network, SLFN)
  • 12寸半导体厂等保安全的设计思路
  • Transfomer的各层矩阵
  • Spring Boot开发编译后读取不到@spring.profiles.active@的问题
  • MySQL的分析查询语句
  • 网络刷卡器的功能和使用场景
  • 无人机森林草原播种施肥植物恢复技术详解
  • Wireshark(1)