Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

L2Wallet SendTransaction is not thread safe #42

Description

@Meteriox

I found that when multiple goroutines concurrently call SendTransaction to send transactions, it's possible to get the same txhash for different transactions. This can lead to some transactions not being confirmed, and calling WaitMined after sending the transaction can't retrieve the transaction information, resulting in a deadlock. The code to reproduce this issue is as follows:
`func main() {
PrivateKey := ""
ZkSyncEraProvider := "
"

client, err := clients.Dial(ZkSyncEraProvider)
if err != nil {
	return
}
defer client.Close()

wallet, err := accounts.NewWalletL2(common.Hex2Bytes(PrivateKey), &client)
if err != nil {
	return
}

SatsAddress := common.HexToAddress("***")
ZBTCAddress := common.HexToAddress("***")
l2address := common.HexToAddress("***")

contractABI, err := abi.JSON(strings.NewReader(erc20ABI))
if err != nil {
	log.Fatal(err)
}

var wg sync.WaitGroup
wg.Add(3) 

go func() {
	defer wg.Done()
	amount := ToWei("1", 18)
	mintData, err := contractABI.Pack("mint", l2address, amount)
	if err != nil {
		return
	}
	hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{
		To:   &SatsAddress,
		Data: mintData,
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	_, err = client.WaitMined(context.Background(), hash)
	if err != nil {
		log.Panic(err)
	}
}()

go func() {
	defer wg.Done()
	amount := ToWei("0.001", 18)
	mintData, err := contractABI.Pack("mint", l2address, amount)
	if err != nil {
		return
	}
	hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{
		To:   &ZBTCAddress,
		Data: mintData,
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	_, err = client.WaitMined(context.Background(), hash)
	if err != nil {
		log.Panic(err)
	}
}()

go func() {
	defer wg.Done()
	amount := ToWei("0.001", 18)
	mintData, err := contractABI.Pack("mint", l2address, amount)
	if err != nil {
		return
	}
	hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{
		To:   &ZBTCAddress,
		Data: mintData,
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	_, err = client.WaitMined(context.Background(), hash)
	if err != nil {
		log.Panic(err)
	}
}()
wg.Wait()

}`

However, after adding a lock to SendTransaction, a new problem arose.
Since the Nonce calculation in PopulateTransaction is based on an RPC call rather than local caching, concurrent calls to SendTransaction can lead to the Nonce not being updated in time.
This can result in transactions with the same Nonce field being generated. When the previous transaction is submitted and successfully processed, the subsequent transaction will be rejected due to an invalid Nonce field.

go func() { defer wg.Done() amount := ToWei("1", 18) mintData, err := contractABI.Pack("mint", l2address, amount) if err != nil { return } lock.Lock() hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ To: &SatsAddress, Data: mintData, }) if err != nil { fmt.Println(err) return } lock.Unlock() _, err = client.WaitMined(context.Background(), hash) if err != nil { log.Panic(err) } }()

In the end, I had to include WaitMined in the locked scope as well, forcing the originally concurrent transactions to be executed and confirmed in a completely serial manner.

go func() { defer wg.Done() amount := ToWei("1", 18) mintData, err := contractABI.Pack("mint", l2address, amount) if err != nil { return } lock.Lock() hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ To: &SatsAddress, Data: mintData, }) if err != nil { fmt.Println(err) return } _, err = client.WaitMined(context.Background(), hash) if err != nil { log.Panic(err) } lock.Unlock() }()

My question is, when using zksync-gosdk, is it only possible to execute transactions serially, and not to send transactions concurrently?
If concurrent transactions are possible, how can it be done? If not, please add a note in the comments of SendTransaction indicating that it is not thread-safe for concurrent use.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions