关于并行写golang合约状态变量的疑问
【问题分类】
- bug
- P2P网络相关(包含libp2p,liquid)
- 链账户身份与权限相关(证书问题、public、多签投票问题)
- 核心交易引擎相关(交易池、DAG)
- 共识相关
- 智能合约相关
- 存储相关
- SDK相关
- 长安链CMC工具
- 长安链管理台
- 长安链浏览器
- 长安链合约IDE
- 长安链web签名插件
- 跨链相关
- 轻节点相关
- 隐私计算相关
- 密码学相关
- 环境依赖
- 其他补充:
【问题描述】(请对问题进行描述,方便定位问题)
package main
import (
"log"
"strconv"
"sync"
"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo"
"chainmaker.org/chainmaker/contract-sdk-go/v2/sandbox"
"chainmaker.org/chainmaker/contract-sdk-go/v2/sdk"
)
type MyContract struct {
x int
sync.Mutex
}
func (f *MyContract) InitContract() protogo.Response {
return sdk.Success([]byte("Init success"))
}
func (f *MyContract) UpgradeContract() protogo.Response {
return sdk.Success([]byte("Upgrade success"))
}
func (f *MyContract) InvokeContract(method string) protogo.Response {
switch method {
case "add":
return f.Add()
case "query":
return f.Query()
default:
return sdk.Error("invalid method")
}
}
func (f *MyContract) Add() protogo.Response {
f.Lock()
defer f.Unlock()
f.x += 1
return sdk.Success(nil)
}
func (f *MyContract) Query() protogo.Response {
f.Lock()
defer f.Unlock()
return sdk.Success([]byte(strconv.Itoa(f.x)))
}
func main() {
err := sandbox.Start(new(MyContract))
if err != nil {
log.Fatal(err)
}
}
上面这是我在长安链上部署的合约,合约存在一个int变量x和一个互斥锁,Add()
方法会加锁并自增一次x,随后我编写了如下代码对这个合约的Add()方法进行并行调用100次,并在调用前后进行x值的查询,在Add()方法调用后会sleep三分钟再进行查询
package demo
import (
"chainmaker-parallel/client"
"chainmaker-parallel/logger"
"chainmaker.org/chainmaker/pb-go/v2/common"
"fmt"
"log"
"sync"
"time"
)
func main() {
logger.Init()
client.Init()
contractname := "contract_name"
fmt.Println("====================== 创建合约 ======================")
resp, err := client.CreateUserContractGOLANG(client.Client, contractname, "1.0.0",
"/root/chainmaker-parallel/test/contract_name.7z", common.RuntimeType_DOCKER_GO, []*common.KeyValuePair{}, true)
if err != nil {
log.Fatalln(err)
}
if err != nil {
logger.Logger.Panic(err)
}
fmt.Println(resp)
kvs := []*common.KeyValuePair{
{
Key: "method",
Value: []byte("add"),
},
}
fmt.Println("====================== 调用合约 ======================")
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
defer wg.Done()
_, err := client.InvokeUserContractWithResult(client.Client, contractname, "invoke_contract", "", kvs, true)
if err != nil {
fmt.Println(err)
}
}()
}
wg.Wait()
fmt.Println("Sleep for 3min")
time.Sleep(time.Minute * 3)
fmt.Println("====================== 执行合约查询接口 ======================")
kvs = []*common.KeyValuePair{
{
Key: "method",
Value: []byte("query"),
},
}
client.UserContractClaimQuery(client.Client, contractname, "invoke_contract", kvs)
}
但是输出结果如下,可以看到调用合约的时候所有交易的结果都没有出错,表示交易成功执行了,但是合约x的值却只增加了1,而不是在所有交易结果都没有出错情况下的100。我猜测可能是因为访问冲突变量导致了交易回滚,但是这样的话交易返回的结果应该含有err,不知道是哪里出了问题
====================== 创建合约 ======================
contract_result:<result:"\n\016contract_name3\022\0051.0.0\030\010*\236\010\n\026wx-org1.chainmaker.org\032\224\007-----BEGIN CERTIFICATE-----\nMIICeDCCAh6gAwIBAgIDDInJMAoGCCqGSM49BAMCMIGKMQswCQYDVQQGEwJDTjEQ\nMA4GA1UECBMHQmVpamluZzEQMA4GA1UEBxMHQmVpamluZzEfMB0GA1UEChMWd3gt\nb3JnMS5jaGFpbm1ha2VyLm9yZzESMBAGA1UECxMJcm9vdC1jZXJ0MSIwIAYDVQQD\nExljYS53eC1vcmcxLmNoYWlubWFrZXIub3JnMB4XDTI1MDMxMTAxNTMyOVoXDTMw\nMDMxMDAxNTMyOVowgZExCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdCZWlqaW5nMRAw\nDgYDVQQHEwdCZWlqaW5nMR8wHQYDVQQKExZ3eC1vcmcxLmNoYWlubWFrZXIub3Jn\nMQ8wDQYDVQQLEwZjbGllbnQxLDAqBgNVBAMTI2NsaWVudDEuc2lnbi53eC1vcmcx\nLmNoYWlubWFrZXIub3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETytUSR2Y\nsE1cOzxntj51E1A8P/hanTznuGzAKlnM8VcROMzdA5nPKfsoL4/ekbpFHirTKmXo\n/g3mK9pKnxl4A6NqMGgwDgYDVR0PAQH/BAQDAgbAMCkGA1UdDgQiBCCI/G1PwSA/\nsuSy4jjOoimRowx9sd0oDIAxPwWnNBSqJzArBgNVHSMEJDAigCDAPJNN+V+E4XD0\nHiDgtaGh3M6WNemT0uWKaJrh745rqDAKBggqhkjOPQQDAgNIADBFAiAudGlMXCRp\nQDGHcvX8HTqtmwjM9nJQIi9284Bd79aBswIhAKgHLI12CWYU46lqwDarvGU1blIJ\nwPrDS35uxAfzqsqU\n-----END CERTIFICATE-----\n\"#client1.sign.wx-org1.chainmaker.org*\006CLIENT2@88fc6d4fc1203fb2e4b2e238cea22991a30c7db1dd280c80313f05a73414aa272(3943da63d19ac656e6f1fc31f02b440dea0107b3" message:"Success" gas_used:13146 > tx_id:"182b9dcb19506ef2cacbda7fe32bf6bbc1524d3edbf84bb2a8597a58d8c03943" tx_timestamp:1741659176 tx_block_height:5
====================== 合约调用前变量取值 ======================
QUERY claim contract resp: message:"SUCCESS" contract_result:<result:"0" message:"Success" gas_used:111 > tx_id:"182b9dcba4df08c0caff0ef95a7ac9b6fd2e052ad4ed4af0bb92f2c949418179"
====================== 调用合约 ======================
Sleep for 3min
====================== 合约调用后变量取值 ======================
QUERY claim contract resp: message:"SUCCESS" contract_result:<result:"1" message:"Success" gas_used:111 > tx_id:"182b9df5c49b9c6acad517
下面这是调用合约的代码,仅当交易成功时才不会返回error
func InvokeUserContractWithResult(client *sdk.ChainClient, contractName, method, txId string,
kvs []*common.KeyValuePair, withSyncResult bool) ([]byte, error) {
resp, err := client.InvokeContract(contractName, method, txId, kvs, -1, withSyncResult)
if err != nil {
return nil, err
}
if resp.Code != common.TxStatusCode_SUCCESS {
return nil, fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]", resp.Code, resp.Message)
}
return resp.ContractResult.Result, nil
}
【相关日志文件】(如果有报错日志请贴图,或者上传附件)
节点日志 log.tar.gz sdk日志 sdk.log
【系统信息】(请填写系统信息,方便定位问题)
- chainmaker-go version * : [v2.3.4]
- OS & version * : ubuntu-22.04.3-live-server-amd64, Linux version 5.15.0-119-generic
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information