Skip to content

Commit ea11f7d

Browse files
holimanfjl
authored andcommitted
internal/ethapi: add mutex around signing + nonce assignment
This prevents concurrent assignment of identical nonces when automatic assignment is used.
1 parent 07aae19 commit ea11f7d

File tree

4 files changed

+93
-37
lines changed

4 files changed

+93
-37
lines changed

eth/bind.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func NewContractBackend(apiBackend ethapi.Backend) *ContractBackend {
4848
return &ContractBackend{
4949
eapi: ethapi.NewPublicEthereumAPI(apiBackend),
5050
bcapi: ethapi.NewPublicBlockChainAPI(apiBackend),
51-
txapi: ethapi.NewPublicTransactionPoolAPI(apiBackend),
51+
txapi: ethapi.NewPublicTransactionPoolAPI(apiBackend, new(ethapi.AddrLocker)),
5252
}
5353
}
5454

internal/ethapi/addrlock.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package ethapi
18+
19+
import (
20+
"sync"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
)
24+
25+
type AddrLocker struct {
26+
mu sync.Mutex
27+
locks map[common.Address]*sync.Mutex
28+
}
29+
30+
// lock returns the lock of the given address.
31+
func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
32+
l.mu.Lock()
33+
defer l.mu.Unlock()
34+
if l.locks == nil {
35+
l.locks = make(map[common.Address]*sync.Mutex)
36+
}
37+
if _, ok := l.locks[address]; !ok {
38+
l.locks[address] = new(sync.Mutex)
39+
}
40+
return l.locks[address]
41+
}
42+
43+
// LockAddr locks an account's mutex. This is used to prevent another tx getting the
44+
// same nonce until the lock is released. The mutex prevents the (an identical nonce) from
45+
// being read again during the time that the first transaction is being signed.
46+
func (l *AddrLocker) LockAddr(address common.Address) {
47+
l.lock(address).Lock()
48+
}
49+
50+
// UnlockAddr unlocks the mutex of the given account.
51+
func (l *AddrLocker) UnlockAddr(address common.Address) {
52+
l.lock(address).Unlock()
53+
}

internal/ethapi/api.go

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"fmt"
2424
"math/big"
2525
"strings"
26-
"sync"
2726
"time"
2827

2928
"github.com/ethereum/go-ethereum/accounts"
@@ -204,12 +203,13 @@ func (s *PublicAccountAPI) Accounts() []common.Address {
204203
// It offers methods to create, (un)lock en list accounts. Some methods accept
205204
// passwords and are therefore considered private by default.
206205
type PrivateAccountAPI struct {
207-
am *accounts.Manager
208-
b Backend
206+
am *accounts.Manager
207+
nonceLock *AddrLocker
208+
b Backend
209209
}
210210

211211
// NewPrivateAccountAPI create a new PrivateAccountAPI.
212-
func NewPrivateAccountAPI(b Backend) *PrivateAccountAPI {
212+
func NewPrivateAccountAPI(b Backend, nonceLock *AddrLocker) *PrivateAccountAPI {
213213
return &PrivateAccountAPI{
214214
am: b.AccountManager(),
215215
b: b,
@@ -316,17 +316,25 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
316316
// tries to sign it with the key associated with args.To. If the given passwd isn't
317317
// able to decrypt the key it fails.
318318
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
319-
// Set some sanity defaults and terminate on failure
320-
if err := args.setDefaults(ctx, s.b); err != nil {
321-
return common.Hash{}, err
322-
}
323319
// Look up the wallet containing the requested signer
324320
account := accounts.Account{Address: args.From}
325321

326322
wallet, err := s.am.Find(account)
327323
if err != nil {
328324
return common.Hash{}, err
329325
}
326+
327+
if args.Nonce == nil {
328+
// Hold the addresse's mutex around signing to prevent concurrent assignment of
329+
// the same nonce to multiple accounts.
330+
s.nonceLock.LockAddr(args.From)
331+
defer s.nonceLock.UnlockAddr(args.From)
332+
}
333+
334+
// Set some sanity defaults and terminate on failure
335+
if err := args.setDefaults(ctx, s.b); err != nil {
336+
return common.Hash{}, err
337+
}
330338
// Assemble the transaction and sign with the wallet
331339
tx := args.toTransaction()
332340

@@ -886,18 +894,13 @@ func newRPCTransaction(b *types.Block, txHash common.Hash) (*RPCTransaction, err
886894

887895
// PublicTransactionPoolAPI exposes methods for the RPC interface
888896
type PublicTransactionPoolAPI struct {
889-
b Backend
897+
b Backend
898+
nonceLock *AddrLocker
890899
}
891900

892-
// nonceMutex is a global mutex for locking the nonce while a transaction
893-
// is being submitted. This should be used when a nonce has not been provided by the user,
894-
// and we get a nonce from the pools. The mutex prevents the (an identical nonce) from being
895-
// read again during the time that the first transaction is being signed.
896-
var nonceMutex sync.RWMutex
897-
898901
// NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
899-
func NewPublicTransactionPoolAPI(b Backend) *PublicTransactionPoolAPI {
900-
return &PublicTransactionPoolAPI{b}
902+
func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI {
903+
return &PublicTransactionPoolAPI{b, nonceLock}
901904
}
902905

903906
func getTransaction(chainDb ethdb.Database, b Backend, txHash common.Hash) (*types.Transaction, bool, error) {
@@ -1176,24 +1179,25 @@ func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
11761179
// transaction pool.
11771180
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
11781181

1179-
if args.Nonce == nil {
1180-
// We'll need to set nonce from pool, and thus we need to lock here
1181-
nonceMutex.Lock()
1182-
defer nonceMutex.Unlock()
1183-
1184-
}
1185-
1186-
// Set some sanity defaults and terminate on failure
1187-
if err := args.setDefaults(ctx, s.b); err != nil {
1188-
return common.Hash{}, err
1189-
}
11901182
// Look up the wallet containing the requested signer
11911183
account := accounts.Account{Address: args.From}
11921184

11931185
wallet, err := s.b.AccountManager().Find(account)
11941186
if err != nil {
11951187
return common.Hash{}, err
11961188
}
1189+
1190+
if args.Nonce == nil {
1191+
// Hold the addresse's mutex around signing to prevent concurrent assignment of
1192+
// the same nonce to multiple accounts.
1193+
s.nonceLock.LockAddr(args.From)
1194+
defer s.nonceLock.UnlockAddr(args.From)
1195+
}
1196+
1197+
// Set some sanity defaults and terminate on failure
1198+
if err := args.setDefaults(ctx, s.b); err != nil {
1199+
return common.Hash{}, err
1200+
}
11971201
// Assemble the transaction and sign with the wallet
11981202
tx := args.toTransaction()
11991203

@@ -1270,14 +1274,12 @@ type SignTransactionResult struct {
12701274
// The node needs to have the private key of the account corresponding with
12711275
// the given from address and it needs to be unlocked.
12721276
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) {
1273-
12741277
if args.Nonce == nil {
1275-
// We'll need to set nonce from pool, and thus we need to lock here
1276-
nonceMutex.Lock()
1277-
defer nonceMutex.Unlock()
1278-
1278+
// Hold the addresse's mutex around signing to prevent concurrent assignment of
1279+
// the same nonce to multiple accounts.
1280+
s.nonceLock.LockAddr(args.From)
1281+
defer s.nonceLock.UnlockAddr(args.From)
12791282
}
1280-
12811283
if err := args.setDefaults(ctx, s.b); err != nil {
12821284
return nil, err
12831285
}

internal/ethapi/backend.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type State interface {
7373
}
7474

7575
func GetAPIs(apiBackend Backend) []rpc.API {
76+
nonceLock := new(AddrLocker)
7677
return []rpc.API{
7778
{
7879
Namespace: "eth",
@@ -87,7 +88,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
8788
}, {
8889
Namespace: "eth",
8990
Version: "1.0",
90-
Service: NewPublicTransactionPoolAPI(apiBackend),
91+
Service: NewPublicTransactionPoolAPI(apiBackend, nonceLock),
9192
Public: true,
9293
}, {
9394
Namespace: "txpool",
@@ -111,7 +112,7 @@ func GetAPIs(apiBackend Backend) []rpc.API {
111112
}, {
112113
Namespace: "personal",
113114
Version: "1.0",
114-
Service: NewPrivateAccountAPI(apiBackend),
115+
Service: NewPrivateAccountAPI(apiBackend, nonceLock),
115116
Public: false,
116117
},
117118
}

0 commit comments

Comments
 (0)