Skip to content

Commit e486ce0

Browse files
author
Jeff Yanta
committed
Setup local nonce pool in gRPC transaction service
1 parent a7ced3b commit e486ce0

File tree

5 files changed

+95
-43
lines changed

5 files changed

+95
-43
lines changed

pkg/code/server/transaction/airdrop.go

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ import (
2525
"github.com/code-payments/code-server/pkg/code/data/currency"
2626
"github.com/code-payments/code-server/pkg/code/data/fulfillment"
2727
"github.com/code-payments/code-server/pkg/code/data/intent"
28-
"github.com/code-payments/code-server/pkg/code/data/nonce"
2928
exchange_rate_util "github.com/code-payments/code-server/pkg/code/exchangerate"
30-
"github.com/code-payments/code-server/pkg/code/transaction"
3129
currency_lib "github.com/code-payments/code-server/pkg/currency"
3230
"github.com/code-payments/code-server/pkg/grpc/client"
3331
"github.com/code-payments/code-server/pkg/pointer"
@@ -297,14 +295,13 @@ func (s *transactionServer) airdrop(ctx context.Context, intentId string, owner
297295
// Instead of constructing and validating everything manually, we could
298296
// have a proper client call SubmitIntent in a worker.
299297

300-
selectedNonce, err := transaction.SelectAvailableNonce(ctx, s.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction)
298+
selectedNonce, err := s.noncePool.GetNonce(ctx)
301299
if err != nil {
302300
log.WithError(err).Warn("failure selecting available nonce")
303301
return nil, err
304302
}
305303
defer func() {
306304
selectedNonce.ReleaseIfNotReserved()
307-
selectedNonce.Unlock()
308305
}()
309306

310307
vixnHash := cvm.GetCompactTransferMessage(&cvm.GetCompactTransferMessageArgs{
@@ -424,34 +421,24 @@ func (s *transactionServer) airdrop(ctx context.Context, intentId string, owner
424421
return intentRecord, nil
425422
}
426423

427-
func (s *transactionServer) mustLoadAirdropper(ctx context.Context) {
428-
log := s.log.WithFields(logrus.Fields{
429-
"method": "mustLoadAirdropper",
430-
"key": s.conf.airdropperOwnerPublicKey.Get(ctx),
431-
})
432-
433-
err := func() error {
434-
vaultRecord, err := s.data.GetKey(ctx, s.conf.airdropperOwnerPublicKey.Get(ctx))
435-
if err != nil {
436-
return err
437-
}
438-
439-
ownerAccount, err := common.NewAccountFromPrivateKeyString(vaultRecord.PrivateKey)
440-
if err != nil {
441-
return err
442-
}
424+
func (s *transactionServer) loadAirdropper(ctx context.Context) error {
425+
vaultRecord, err := s.data.GetKey(ctx, s.conf.airdropperOwnerPublicKey.Get(ctx))
426+
if err != nil {
427+
return err
428+
}
443429

444-
timelockAccounts, err := ownerAccount.GetTimelockAccounts(common.CodeVmAccount, common.CoreMintAccount)
445-
if err != nil {
446-
return err
447-
}
430+
ownerAccount, err := common.NewAccountFromPrivateKeyString(vaultRecord.PrivateKey)
431+
if err != nil {
432+
return err
433+
}
448434

449-
s.airdropper = timelockAccounts
450-
return nil
451-
}()
435+
timelockAccounts, err := ownerAccount.GetTimelockAccounts(common.CodeVmAccount, common.CoreMintAccount)
452436
if err != nil {
453-
log.WithError(err).Fatal("failure loading account")
437+
return err
454438
}
439+
440+
s.airdropper = timelockAccounts
441+
return nil
455442
}
456443

457444
func GetOldAirdropIntentId(airdropType AirdropType, reference string) string {

pkg/code/server/transaction/intent.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"github.com/code-payments/code-server/pkg/code/data/action"
2626
"github.com/code-payments/code-server/pkg/code/data/fulfillment"
2727
"github.com/code-payments/code-server/pkg/code/data/intent"
28-
"github.com/code-payments/code-server/pkg/code/data/nonce"
2928
"github.com/code-payments/code-server/pkg/code/data/timelock"
3029
"github.com/code-payments/code-server/pkg/code/transaction"
3130
"github.com/code-payments/code-server/pkg/grpc/client"
@@ -300,7 +299,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
300299
var actionHandlers []CreateActionHandler
301300
var actionRecords []*action.Record
302301
var fulfillments []fulfillmentWithSigningMetadata
303-
var reservedNonces []*transaction.SelectedNonce
302+
var reservedNonces []*transaction.Nonce
304303
var serverParameters []*transactionpb.ServerParameter
305304
for i, protoAction := range submitActionsReq.Actions {
306305
log := log.WithField("action_id", i)
@@ -368,11 +367,11 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
368367

369368
// Select any available nonce reserved for use for a client transaction,
370369
// if it's required
371-
var selectedNonce *transaction.SelectedNonce
370+
var selectedNonce *transaction.Nonce
372371
var nonceAccount *common.Account
373372
var nonceBlockchash solana.Blockhash
374373
if actionHandler.RequiresNonce(j) {
375-
selectedNonce, err = transaction.SelectAvailableNonce(ctx, s.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction)
374+
selectedNonce, err = s.noncePool.GetNonce(ctx)
376375
if err != nil {
377376
log.WithError(err).Warn("failure selecting available nonce")
378377
return handleSubmitIntentError(streamer, err)
@@ -383,7 +382,6 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
383382
// caused a failed RPC call, and we want to avoid malicious or erroneous
384383
// clients from consuming our nonce pool!
385384
selectedNonce.ReleaseIfNotReserved()
386-
selectedNonce.Unlock()
387385
}()
388386
nonceAccount = selectedNonce.Account
389387
nonceBlockchash = selectedNonce.Blockhash

pkg/code/server/transaction/server.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"sync"
66

7+
"github.com/pkg/errors"
78
"github.com/sirupsen/logrus"
89

910
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
@@ -13,6 +14,8 @@ import (
1314
auth_util "github.com/code-payments/code-server/pkg/code/auth"
1415
"github.com/code-payments/code-server/pkg/code/common"
1516
code_data "github.com/code-payments/code-server/pkg/code/data"
17+
"github.com/code-payments/code-server/pkg/code/data/nonce"
18+
"github.com/code-payments/code-server/pkg/code/transaction"
1619
"github.com/code-payments/code-server/pkg/jupiter"
1720
sync_util "github.com/code-payments/code-server/pkg/sync"
1821
)
@@ -30,6 +33,8 @@ type transactionServer struct {
3033
antispamGuard *antispam.Guard
3134
amlGuard *aml.Guard
3235

36+
noncePool *transaction.LocalNoncePool
37+
3338
airdropperLock sync.Mutex
3439
airdropper *common.TimelockAccounts
3540

@@ -53,14 +58,26 @@ func NewTransactionServer(
5358
airdropIntegration AirdropIntegration,
5459
antispamGuard *antispam.Guard,
5560
amlGuard *aml.Guard,
61+
noncePool *transaction.LocalNoncePool,
5662
configProvider ConfigProvider,
57-
) transactionpb.TransactionServer {
63+
) (transactionpb.TransactionServer, error) {
5864
ctx := context.Background()
5965

6066
conf := configProvider()
6167

6268
stripedLockParallelization := uint(conf.stripedLockParallelization.Get(ctx))
6369

70+
noncePoolEnv, noncePoolEnvInstance, noncePoolType := noncePool.GetConfiguration()
71+
if noncePoolEnv != nonce.EnvironmentCvm {
72+
return nil, errors.Errorf("nonce pool environment must be %s", nonce.EnvironmentCvm)
73+
}
74+
if noncePoolEnvInstance != common.CodeVmAccount.PublicKey().ToBase58() {
75+
return nil, errors.Errorf("nonce pool environment instance must be %s", common.CodeVmAccount.PublicKey().ToBase58())
76+
}
77+
if noncePoolType != nonce.PurposeClientTransaction {
78+
return nil, errors.Errorf("nonce pool type must be %s", nonce.PurposeClientTransaction)
79+
}
80+
6481
s := &transactionServer{
6582
log: logrus.StandardLogger().WithField("type", "transaction/v2/server"),
6683
conf: conf,
@@ -74,15 +91,20 @@ func NewTransactionServer(
7491
antispamGuard: antispamGuard,
7592
amlGuard: amlGuard,
7693

94+
noncePool: noncePool,
95+
7796
intentLocks: sync_util.NewStripedLock(stripedLockParallelization),
7897
ownerLocks: sync_util.NewStripedLock(stripedLockParallelization),
7998
giftCardLocks: sync_util.NewStripedLock(stripedLockParallelization),
8099
}
81100

82101
airdropper := s.conf.airdropperOwnerPublicKey.Get(ctx)
83102
if len(airdropper) > 0 && airdropper != defaultAirdropperOwnerPublicKey {
84-
s.mustLoadAirdropper(ctx)
103+
err := s.loadAirdropper(ctx)
104+
if err != nil {
105+
return nil, err
106+
}
85107
}
86108

87-
return s
109+
return s, nil
88110
}

pkg/code/transaction/nonce_pool.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ package transaction
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"slices"
87
"sync"
98
"time"
109

1110
"github.com/google/uuid"
11+
"github.com/mr-tron/base58/base58"
1212
"github.com/newrelic/go-agent/v3/newrelic"
13+
"github.com/pkg/errors"
1314
"github.com/sirupsen/logrus"
1415

16+
"github.com/code-payments/code-server/pkg/code/common"
1517
code_data "github.com/code-payments/code-server/pkg/code/data"
1618
"github.com/code-payments/code-server/pkg/code/data/nonce"
1719
"github.com/code-payments/code-server/pkg/pointer"
20+
"github.com/code-payments/code-server/pkg/solana"
1821
)
1922

2023
var (
@@ -153,6 +156,9 @@ func (opts *noncePoolOpts) validate() error {
153156

154157
// Nonce represents a handle to a nonce that is owned by a local nonce pool.
155158
type Nonce struct {
159+
Account *common.Account
160+
Blockhash solana.Blockhash
161+
156162
pool *LocalNoncePool
157163
record *nonce.Record
158164
}
@@ -317,6 +323,10 @@ func (np *LocalNoncePool) GetNonce(ctx context.Context) (*Nonce, error) {
317323
return n, nil
318324
}
319325

326+
func (np *LocalNoncePool) GetConfiguration() (nonce.Environment, string, nonce.Purpose) {
327+
return np.env, np.envInstance, np.poolType
328+
}
329+
320330
func (np *LocalNoncePool) Close() error {
321331
log := np.log.WithField("method", "Close")
322332

@@ -376,13 +386,33 @@ func (np *LocalNoncePool) load(ctx context.Context, limit int) (int, error) {
376386
return 0, ErrNoAvailableNonces
377387
}
378388

379-
np.mu.Lock()
380-
for i := range records {
381-
np.freeList = append(np.freeList, &Nonce{pool: np, record: records[i]})
389+
var newNonces []*Nonce
390+
for _, record := range records {
391+
account, err := common.NewAccountFromPublicKeyString(record.Address)
392+
if err != nil {
393+
return 0, errors.Wrap(err, "invalid address")
394+
}
395+
396+
decodedBh, err := base58.Decode(record.Blockhash)
397+
if err != nil {
398+
return 0, errors.Wrap(err, "invalid blochash")
399+
}
400+
var bh solana.Blockhash
401+
copy(bh[:], decodedBh)
402+
403+
newNonces = append(newNonces, &Nonce{
404+
Account: account,
405+
Blockhash: bh,
406+
pool: np,
407+
record: record,
408+
})
382409
}
410+
411+
np.mu.Lock()
412+
np.freeList = append(np.freeList, newNonces...)
383413
np.mu.Unlock()
384414

385-
return len(records), nil
415+
return len(newNonces), nil
386416
}
387417

388418
func (np *LocalNoncePool) refreshPool() {

pkg/code/transaction/nonce_pool_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ package transaction
22

33
import (
44
"context"
5+
"crypto/rand"
56
"fmt"
67
"testing"
78
"time"
89

10+
"github.com/mr-tron/base58"
911
"github.com/stretchr/testify/require"
1012

13+
"github.com/code-payments/code-server/pkg/code/common"
1114
code_data "github.com/code-payments/code-server/pkg/code/data"
1215
"github.com/code-payments/code-server/pkg/code/data/nonce"
16+
"github.com/code-payments/code-server/pkg/solana"
1317
"github.com/code-payments/code-server/pkg/testutil"
1418
)
1519

@@ -60,6 +64,8 @@ func testLocalNoncePoolHappyPath(nt *localNoncePoolTest) {
6064
require.NoError(nt.t, err)
6165
require.NotNil(nt.t, n)
6266
require.NotContains(nt.t, observed, n.record.Address)
67+
require.Equal(nt.t, n.Account.PublicKey().ToBase58(), n.record.Address)
68+
require.Equal(nt.t, base58.Encode(n.Blockhash[:]), n.record.Blockhash)
6369
observed[n.record.Address] = n
6470

6571
actual, err := nt.data.GetNonce(ctx, n.record.Address)
@@ -157,6 +163,8 @@ func testLocalNoncePoolReload(nt *localNoncePoolTest) {
157163
require.NoError(nt.t, err)
158164
require.NotNil(nt.t, n)
159165
require.NotContains(nt.t, observed, n.record.Address)
166+
require.Equal(nt.t, n.Account.PublicKey().ToBase58(), n.record.Address)
167+
require.Equal(nt.t, base58.Encode(n.Blockhash[:]), n.record.Blockhash)
160168
observed[n.record.Address] = n
161169

162170
actual, err := nt.data.GetNonce(ctx, n.record.Address)
@@ -193,6 +201,8 @@ func testLocalNoncePoolRefresh(nt *localNoncePoolTest) {
193201
require.NoError(nt.t, err)
194202
require.NotNil(nt.t, n)
195203
require.NotContains(nt.t, observed, n.record.Address)
204+
require.Equal(nt.t, n.Account.PublicKey().ToBase58(), n.record.Address)
205+
require.Equal(nt.t, base58.Encode(n.Blockhash[:]), n.record.Blockhash)
196206
observed[n.record.Address] = n
197207

198208
actual, err := nt.data.GetNonce(ctx, n.record.Address)
@@ -235,6 +245,8 @@ type localNoncePoolTest struct {
235245
func newLocalNoncePoolTest(t *testing.T) *localNoncePoolTest {
236246
data := code_data.NewTestDataProvider()
237247

248+
testutil.SetupRandomSubsidizer(t, data)
249+
238250
pool, err := NewLocalNoncePool(
239251
data,
240252
nil,
@@ -257,9 +269,12 @@ func newLocalNoncePoolTest(t *testing.T) *localNoncePoolTest {
257269

258270
func (np *localNoncePoolTest) initializeNonces(amount int, env nonce.Environment, envInstance string, purpose nonce.Purpose) {
259271
for i := 0; i < amount; i++ {
272+
var bh solana.Blockhash
273+
rand.Read(bh[:])
260274
err := np.data.SaveNonce(context.Background(), &nonce.Record{
261-
Address: fmt.Sprintf("addr-%s-%s-%s-%d", env.String(), envInstance, purpose.String(), i),
262-
Authority: "my authority!",
275+
Address: testutil.NewRandomAccount(np.t).PublicKey().ToBase58(),
276+
Blockhash: base58.Encode(bh[:]),
277+
Authority: common.GetSubsidizer().PublicKey().ToBase58(),
263278
Environment: env,
264279
EnvironmentInstance: envInstance,
265280
Purpose: purpose,

0 commit comments

Comments
 (0)