-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move nonce generation from mpool to wallet
- Loading branch information
Showing
5 changed files
with
307 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package messagesigner | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
|
||
"github.com/filecoin-project/lotus/chain/wallet" | ||
|
||
"github.com/filecoin-project/lotus/chain/messagepool" | ||
|
||
"github.com/filecoin-project/go-address" | ||
"github.com/filecoin-project/lotus/chain/types" | ||
"github.com/filecoin-project/lotus/node/modules/dtypes" | ||
"github.com/ipfs/go-datastore" | ||
"github.com/ipfs/go-datastore/namespace" | ||
cbg "github.com/whyrusleeping/cbor-gen" | ||
"golang.org/x/xerrors" | ||
) | ||
|
||
const dsKeyActorNonce = "ActorNonce" | ||
|
||
type mpoolAPI interface { | ||
GetNonce(address.Address) (uint64, error) | ||
} | ||
|
||
// MessageSigner keeps track of nonces per address, and increments the nonce | ||
// when signing a message | ||
type MessageSigner struct { | ||
wallet *wallet.Wallet | ||
mpool mpoolAPI | ||
ds datastore.Batching | ||
} | ||
|
||
func NewMessageSigner(wallet *wallet.Wallet, mpool *messagepool.MessagePool, ds dtypes.MetadataDS) *MessageSigner { | ||
return newMessageSigner(wallet, mpool, ds) | ||
} | ||
|
||
func newMessageSigner(wallet *wallet.Wallet, mpool mpoolAPI, ds dtypes.MetadataDS) *MessageSigner { | ||
ds = namespace.Wrap(ds, datastore.NewKey("/message-signer/")) | ||
return &MessageSigner{ | ||
wallet: wallet, | ||
mpool: mpool, | ||
ds: ds, | ||
} | ||
} | ||
|
||
// SignMessage increments the nonce for the message From address, and signs | ||
// the message | ||
func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message) (*types.SignedMessage, error) { | ||
nonce, err := ms.nextNonce(msg.From) | ||
if err != nil { | ||
return nil, xerrors.Errorf("failed to create nonce: %w", err) | ||
} | ||
|
||
msg.Nonce = nonce | ||
sig, err := ms.wallet.Sign(ctx, msg.From, msg.Cid().Bytes()) | ||
if err != nil { | ||
return nil, xerrors.Errorf("failed to sign message: %w", err) | ||
} | ||
|
||
return &types.SignedMessage{ | ||
Message: *msg, | ||
Signature: *sig, | ||
}, nil | ||
} | ||
|
||
// nextNonce increments the nonce. | ||
// If there is no nonce in the datastore, gets the nonce from the message pool. | ||
func (ms *MessageSigner) nextNonce(addr address.Address) (uint64, error) { | ||
addrNonceKey := datastore.KeyWithNamespaces([]string{dsKeyActorNonce, addr.String()}) | ||
|
||
// Get the nonce for this address from the datastore | ||
nonceBytes, err := ms.ds.Get(addrNonceKey) | ||
|
||
var nonce uint64 | ||
switch { | ||
case xerrors.Is(err, datastore.ErrNotFound): | ||
// If a nonce for this address hasn't yet been created in the | ||
// datastore, check the mempool - nonces used to be created by | ||
// the mempool so we need to support nodes that still have mempool | ||
// nonces. Note that the mempool returns the actor state's nonce by | ||
// default. | ||
nonce, err = ms.mpool.GetNonce(addr) | ||
if err != nil { | ||
return 0, xerrors.Errorf("failed to get nonce from mempool: %w", err) | ||
} | ||
|
||
case err != nil: | ||
return 0, xerrors.Errorf("failed to get nonce from datastore: %w", err) | ||
|
||
default: | ||
// There is a nonce in the mempool, so unmarshall and increment it | ||
maj, val, err := cbg.CborReadHeader(bytes.NewReader(nonceBytes)) | ||
if err != nil { | ||
return 0, xerrors.Errorf("failed to parse nonce from datastore: %w", err) | ||
} | ||
if maj != cbg.MajUnsignedInt { | ||
return 0, xerrors.Errorf("bad cbor type parsing nonce from datastore") | ||
} | ||
|
||
nonce = val + 1 | ||
} | ||
|
||
// Write the nonce for this address to the datastore | ||
buf := bytes.Buffer{} | ||
_, err = buf.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, nonce)) | ||
if err != nil { | ||
return 0, xerrors.Errorf("failed to marshall nonce: %w", err) | ||
} | ||
err = ms.ds.Put(addrNonceKey, buf.Bytes()) | ||
if err != nil { | ||
return 0, xerrors.Errorf("failed to write nonce to datastore: %w", err) | ||
} | ||
|
||
return nonce, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package messagesigner | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/filecoin-project/lotus/chain/wallet" | ||
|
||
"github.com/filecoin-project/go-state-types/crypto" | ||
"github.com/stretchr/testify/require" | ||
|
||
ds_sync "github.com/ipfs/go-datastore/sync" | ||
|
||
"github.com/filecoin-project/go-address" | ||
|
||
"github.com/filecoin-project/lotus/chain/types" | ||
"github.com/ipfs/go-datastore" | ||
) | ||
|
||
type mockMpool struct { | ||
lk sync.RWMutex | ||
nonces map[address.Address]uint64 | ||
} | ||
|
||
func newMockMpool() *mockMpool { | ||
return &mockMpool{nonces: make(map[address.Address]uint64)} | ||
} | ||
|
||
func (mp *mockMpool) setNonce(addr address.Address, nonce uint64) { | ||
mp.lk.Lock() | ||
defer mp.lk.Unlock() | ||
|
||
mp.nonces[addr] = nonce | ||
} | ||
|
||
func (mp *mockMpool) GetNonce(addr address.Address) (uint64, error) { | ||
mp.lk.RLock() | ||
defer mp.lk.RUnlock() | ||
|
||
return mp.nonces[addr], nil | ||
} | ||
|
||
func TestMessageSignerSignMessage(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
w, _ := wallet.NewWallet(wallet.NewMemKeyStore()) | ||
from1, err := w.GenerateKey(crypto.SigTypeSecp256k1) | ||
require.NoError(t, err) | ||
from2, err := w.GenerateKey(crypto.SigTypeSecp256k1) | ||
require.NoError(t, err) | ||
to1, err := w.GenerateKey(crypto.SigTypeSecp256k1) | ||
require.NoError(t, err) | ||
to2, err := w.GenerateKey(crypto.SigTypeSecp256k1) | ||
require.NoError(t, err) | ||
|
||
type msgSpec struct { | ||
msg *types.Message | ||
mpoolNonce [1]uint64 | ||
expNonce uint64 | ||
} | ||
tests := []struct { | ||
name string | ||
msgs []msgSpec | ||
}{{ | ||
// No nonce yet in datastore | ||
name: "no nonce yet", | ||
msgs: []msgSpec{{ | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
expNonce: 0, | ||
}}, | ||
}, { | ||
// Get nonce value of zero from mpool | ||
name: "mpool nonce zero", | ||
msgs: []msgSpec{{ | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
mpoolNonce: [1]uint64{0}, | ||
expNonce: 0, | ||
}}, | ||
}, { | ||
// Get non-zero nonce value from mpool | ||
name: "mpool nonce set", | ||
msgs: []msgSpec{{ | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
mpoolNonce: [1]uint64{5}, | ||
expNonce: 5, | ||
}, { | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
// Should ignore mpool nonce because after the first message nonce | ||
// will come from the datastore | ||
mpoolNonce: [1]uint64{10}, | ||
expNonce: 6, | ||
}}, | ||
}, { | ||
// Nonce should increment independently for each address | ||
name: "nonce increments per address", | ||
msgs: []msgSpec{{ | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
expNonce: 0, | ||
}, { | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
expNonce: 1, | ||
}, { | ||
msg: &types.Message{ | ||
To: to2, | ||
From: from2, | ||
}, | ||
mpoolNonce: [1]uint64{5}, | ||
expNonce: 5, | ||
}, { | ||
msg: &types.Message{ | ||
To: to2, | ||
From: from2, | ||
}, | ||
expNonce: 6, | ||
}, { | ||
msg: &types.Message{ | ||
To: to1, | ||
From: from1, | ||
}, | ||
expNonce: 2, | ||
}}, | ||
}} | ||
for _, tt := range tests { | ||
tt := tt | ||
t.Run(tt.name, func(t *testing.T) { | ||
mpool := newMockMpool() | ||
ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) | ||
ms := newMessageSigner(w, mpool, ds) | ||
|
||
for _, m := range tt.msgs { | ||
if len(m.mpoolNonce) == 1 { | ||
mpool.setNonce(m.msg.From, m.mpoolNonce[0]) | ||
} | ||
smsg, err := ms.SignMessage(ctx, m.msg) | ||
require.NoError(t, err) | ||
require.Equal(t, m.expNonce, smsg.Message.Nonce) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.