package messagesigner import ( "context" "sync" "testing" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/wallet" "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 } var _ MpoolNonceAPI = (*mockMpool)(nil) 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(_ context.Context, addr address.Address, _ types.TipSetKey) (uint64, error) { mp.lk.RLock() defer mp.lk.RUnlock() return mp.nonces[addr], nil } func (mp *mockMpool) GetActor(_ context.Context, addr address.Address, _ types.TipSetKey) (*types.Actor, error) { panic("don't use it") } func TestMessageSignerSignMessage(t *testing.T) { ctx := context.Background() w, _ := wallet.NewWallet(wallet.NewMemKeyStore()) from1, err := w.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) from2, err := w.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) to1, err := w.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) to2, err := w.WalletNew(ctx, types.KTSecp256k1) require.NoError(t, err) type msgSpec struct { msg *types.Message mpoolNonce [1]uint64 expNonce uint64 cbErr error } 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 adjust datastore nonce because mpool nonce is higher mpoolNonce: [1]uint64{10}, expNonce: 10, }}, }, { // 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, }}, }, { name: "recover from callback error", msgs: []msgSpec{{ // No nonce yet in datastore msg: &types.Message{ To: to1, From: from1, }, expNonce: 0, }, { // Increment nonce msg: &types.Message{ To: to1, From: from1, }, expNonce: 1, }, { // Callback returns error msg: &types.Message{ To: to1, From: from1, }, cbErr: xerrors.Errorf("err"), }, { // Callback successful, should increment nonce in datastore 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]) } merr := m.cbErr smsg, err := ms.SignMessage(ctx, m.msg, func(message *types.SignedMessage) error { return merr }) if m.cbErr != nil { require.Error(t, err) require.Nil(t, smsg) } else { require.NoError(t, err) require.Equal(t, m.expNonce, smsg.Message.Nonce) } } }) } }