Skip to content

Commit 2d0616d

Browse files
committed
Implement request owner in GetTokenAccountInfos
1 parent 1678ad5 commit 2d0616d

File tree

6 files changed

+188
-67
lines changed

6 files changed

+188
-67
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
filippo.io/edwards25519 v1.1.0
77
github.com/aws/aws-sdk-go-v2 v0.17.0
88
github.com/bits-and-blooms/bloom/v3 v3.1.0
9-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052
9+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250605155512-63da5d11d58a
1010
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba
1111
github.com/emirpasic/gods v1.12.0
1212
github.com/envoyproxy/protoc-gen-validate v1.2.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
8080
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
8181
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
8282
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
83-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052 h1:lfxaakPHAWFPukrqsUn8nYdpw1WaXQfP4KLCzmL8UxU=
84-
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
83+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250605155512-63da5d11d58a h1:h5AFZjmn+Zzkkd0u2Y+h9msj7HYBOSI3l4i5CD0ls34=
84+
github.com/code-payments/code-protobuf-api v1.19.1-0.20250605155512-63da5d11d58a/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
8585
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba h1:Bkp+gmeb6Y2PWXfkSCTMBGWkb2P1BujRDSjWeI+0j5I=
8686
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba/go.mod h1:jSiifpiBpyBQ8q2R0MGEbkSgWC6sbdRTyDBntmW+j1E=
8787
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=

pkg/code/auth/signature.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ var defaultMarshalStrategies = []marshalStrategy{
6868
}
6969

7070
func (v *RPCSignatureVerifier) isSignatureVerifiedProtoMessage(owner *common.Account, message proto.Message, signature *commonpb.Signature) (bool, error) {
71+
if signature == nil {
72+
return false, nil
73+
}
74+
7175
for _, marshalStrategy := range defaultMarshalStrategies {
7276
messageBytes, err := marshalStrategy(message)
7377
if err != nil {

pkg/code/auth/signature_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func TestAuthenticate(t *testing.T) {
5454
err = env.verifier.Authenticate(env.ctx, ownerAccount, msg, signatureProto)
5555
require.NoError(t, err)
5656

57+
err = env.verifier.Authenticate(env.ctx, ownerAccount, msg, nil)
58+
testutil.AssertStatusErrorWithCode(t, err, codes.Unauthenticated)
59+
5760
signature, err = maliciousAccount.Sign(msgBytes)
5861
require.NoError(t, err)
5962
signatureProto = &commonpb.Signature{

pkg/code/server/account/server.go

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"time"
77

8+
"github.com/golang/protobuf/proto"
89
"github.com/sirupsen/logrus"
910
"google.golang.org/grpc/codes"
1011
"google.golang.org/grpc/status"
@@ -102,15 +103,44 @@ func (s *server) GetTokenAccountInfos(ctx context.Context, req *accountpb.GetTok
102103
}
103104
log = log.WithField("owner_account", owner.PublicKey().ToBase58())
104105

105-
signature := req.Signature
106+
ownerSignature := req.Signature
106107
req.Signature = nil
107-
if err := s.auth.Authenticate(ctx, owner, req, signature); err != nil {
108+
109+
var requestingOwner *common.Account
110+
var requestingOwnerSignature *commonpb.Signature
111+
if req.RequestingOwner != nil {
112+
requestingOwner, err = common.NewAccountFromProto(req.RequestingOwner)
113+
if err != nil {
114+
log.WithError(err).Warn("invalid requesting owner account")
115+
return nil, status.Error(codes.Internal, "")
116+
}
117+
log = log.WithField("requesting_owner_account", requestingOwner.PublicKey().ToBase58())
118+
119+
requestingOwnerSignature = req.RequestingOwnerSignature
120+
req.RequestingOwnerSignature = nil
121+
}
122+
123+
if err := s.auth.Authenticate(ctx, owner, req, ownerSignature); err != nil {
108124
return nil, err
109125
}
126+
if requestingOwner != nil {
127+
if err := s.auth.Authenticate(ctx, requestingOwner, req, requestingOwnerSignature); err != nil {
128+
return nil, err
129+
}
130+
}
110131

111132
cachedResp, ok := giftCardCacheByOwner.Retrieve(owner.PublicKey().ToBase58())
112133
if ok {
113-
return cachedResp.(*accountpb.GetTokenAccountInfosResponse), nil
134+
cachedResp := cachedResp.(*accountpb.GetTokenAccountInfosResponse)
135+
136+
s.updateCachedResponse(cachedResp)
137+
138+
resp, err := s.addRequestingOwnerMetadata(ctx, cachedResp, requestingOwner)
139+
if err != nil {
140+
log.WithError(err).Warn("failure adding requesting owner metadata")
141+
return nil, status.Error(codes.Internal, "")
142+
}
143+
return resp, nil
114144
}
115145

116146
// Fetch all account records
@@ -175,6 +205,11 @@ func (s *server) GetTokenAccountInfos(ctx context.Context, req *accountpb.GetTok
175205
}
176206
}
177207

208+
resp, err = s.addRequestingOwnerMetadata(ctx, resp, requestingOwner)
209+
if err != nil {
210+
log.WithError(err).Warn("failure adding requesting owner metadata")
211+
return nil, status.Error(codes.Internal, "")
212+
}
178213
return resp, nil
179214
}
180215

@@ -397,3 +432,46 @@ func (s *server) getOriginalGiftCardExchangeData(ctx context.Context, records *c
397432
Quarks: intentRecord.SendPublicPaymentMetadata.Quantity,
398433
}, nil
399434
}
435+
436+
func (s *server) addRequestingOwnerMetadata(ctx context.Context, resp *accountpb.GetTokenAccountInfosResponse, requestingOwner *common.Account) (*accountpb.GetTokenAccountInfosResponse, error) {
437+
if requestingOwner == nil {
438+
return resp, nil
439+
}
440+
441+
cloned := proto.Clone(resp).(*accountpb.GetTokenAccountInfosResponse)
442+
443+
for _, ai := range cloned.TokenAccountInfos {
444+
switch ai.AccountType {
445+
case commonpb.AccountType_REMOTE_SEND_GIFT_CARD:
446+
giftCardVaultAccount, err := common.NewAccountFromProto(ai.Address)
447+
if err != nil {
448+
return nil, err
449+
}
450+
451+
intentRecord, err := s.data.GetOriginalGiftCardIssuedIntent(ctx, giftCardVaultAccount.PublicKey().ToBase58())
452+
if err != nil {
453+
return nil, err
454+
}
455+
456+
if intentRecord.InitiatorOwnerAccount == requestingOwner.PublicKey().ToBase58() {
457+
ai.IsGiftCardIssuer = true
458+
}
459+
}
460+
}
461+
462+
return cloned, nil
463+
}
464+
465+
func (s *server) updateCachedResponse(resp *accountpb.GetTokenAccountInfosResponse) {
466+
for _, ai := range resp.TokenAccountInfos {
467+
switch ai.AccountType {
468+
case commonpb.AccountType_REMOTE_SEND_GIFT_CARD:
469+
// Transition any gift card records to expired if we elapsed the expiry window
470+
if time.Since(ai.CreatedAt.AsTime()) >= async_account.GiftCardExpiry {
471+
ai.ClaimState = accountpb.TokenAccountInfo_CLAIM_STATE_EXPIRED
472+
ai.BalanceSource = accountpb.TokenAccountInfo_BALANCE_SOURCE_CACHE
473+
ai.Balance = 0
474+
}
475+
}
476+
}
477+
}

pkg/code/server/account/server_test.go

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -363,26 +363,16 @@ func TestGetTokenAccountInfos_RemoteSendGiftCard_HappyPath(t *testing.T) {
363363
expectedClaimState: accountpb.TokenAccountInfo_CLAIM_STATE_CLAIMED,
364364
},
365365
} {
366-
ownerAccount := testutil.NewRandomAccount(t)
367-
timelockAccounts, err := ownerAccount.GetTimelockAccounts(common.CodeVmAccount, common.CoreMintAccount)
368-
require.NoError(t, err)
366+
giftCardIssuerOwnerAccount := testutil.NewRandomAccount(t)
367+
giftCardOwnerAccount := testutil.NewRandomAccount(t)
369368

370-
req := &accountpb.GetTokenAccountInfosRequest{
371-
Owner: ownerAccount.ToProto(),
372-
}
373-
reqBytes, err := proto.Marshal(req)
374-
require.NoError(t, err)
375-
req.Signature = &commonpb.Signature{
376-
Value: ed25519.Sign(ownerAccount.PrivateKey().ToBytes(), reqBytes),
377-
}
378-
379-
accountRecords := setupAccountRecords(t, env, ownerAccount, ownerAccount, 0, commonpb.AccountType_REMOTE_SEND_GIFT_CARD)
369+
accountRecords := setupAccountRecords(t, env, giftCardOwnerAccount, giftCardOwnerAccount, 0, commonpb.AccountType_REMOTE_SEND_GIFT_CARD)
380370

381371
giftCardIssuedIntentRecord := &intent.Record{
382372
IntentId: testutil.NewRandomAccount(t).PublicKey().ToBase58(),
383373
IntentType: intent.SendPublicPayment,
384374

385-
InitiatorOwnerAccount: testutil.NewRandomAccount(t).PrivateKey().ToBase58(),
375+
InitiatorOwnerAccount: giftCardIssuerOwnerAccount.PublicKey().ToBase58(),
386376

387377
SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{
388378
DestinationTokenAccount: accountRecords.General.TokenAccount,
@@ -444,43 +434,73 @@ func TestGetTokenAccountInfos_RemoteSendGiftCard_HappyPath(t *testing.T) {
444434
accountRecords.Timelock.Block += 1
445435
require.NoError(t, env.data.SaveTimelock(env.ctx, accountRecords.Timelock))
446436

447-
resp, err := env.client.GetTokenAccountInfos(env.ctx, req)
448-
require.NoError(t, err)
449-
assert.Equal(t, accountpb.GetTokenAccountInfosResponse_OK, resp.Result)
450-
assert.Len(t, resp.TokenAccountInfos, 1)
437+
for _, requestingOwnerAccount := range []*common.Account{
438+
nil,
439+
testutil.NewRandomAccount(t),
440+
giftCardIssuerOwnerAccount,
441+
} {
442+
timelockAccounts, err := giftCardOwnerAccount.GetTimelockAccounts(common.CodeVmAccount, common.CoreMintAccount)
443+
require.NoError(t, err)
451444

452-
accountInfo, ok := resp.TokenAccountInfos[timelockAccounts.Vault.PublicKey().ToBase58()]
453-
require.True(t, ok)
445+
req := &accountpb.GetTokenAccountInfosRequest{
446+
Owner: giftCardOwnerAccount.ToProto(),
447+
}
448+
if requestingOwnerAccount != nil {
449+
req.RequestingOwner = requestingOwnerAccount.ToProto()
450+
}
451+
reqBytes, err := proto.Marshal(req)
452+
require.NoError(t, err)
453+
sig1 := &commonpb.Signature{
454+
Value: ed25519.Sign(giftCardOwnerAccount.PrivateKey().ToBytes(), reqBytes),
455+
}
456+
if requestingOwnerAccount != nil {
457+
sig2 := &commonpb.Signature{
458+
Value: ed25519.Sign(requestingOwnerAccount.PrivateKey().ToBytes(), reqBytes),
459+
}
460+
req.RequestingOwnerSignature = sig2
461+
}
462+
req.Signature = sig1
454463

455-
assert.Equal(t, commonpb.AccountType_REMOTE_SEND_GIFT_CARD, accountInfo.AccountType)
456-
assert.Equal(t, ownerAccount.PublicKey().ToBytes(), accountInfo.Owner.Value)
457-
assert.Equal(t, ownerAccount.PublicKey().ToBytes(), accountInfo.Authority.Value)
458-
assert.Equal(t, timelockAccounts.Vault.PublicKey().ToBytes(), accountInfo.Address.Value)
459-
assert.Equal(t, common.CoreMintAccount.PublicKey().ToBytes(), accountInfo.Mint.Value)
460-
assert.EqualValues(t, 0, accountInfo.Index)
464+
resp, err := env.client.GetTokenAccountInfos(env.ctx, req)
465+
require.NoError(t, err)
466+
assert.Equal(t, accountpb.GetTokenAccountInfosResponse_OK, resp.Result)
467+
assert.Len(t, resp.TokenAccountInfos, 1)
461468

462-
assert.Equal(t, tc.expectedBalanceSource, accountInfo.BalanceSource)
463-
if tc.simulateClaimInCode || tc.simulateAutoReturnInCode || tc.expectedClaimState == accountpb.TokenAccountInfo_CLAIM_STATE_CLAIMED || tc.expectedClaimState == accountpb.TokenAccountInfo_CLAIM_STATE_EXPIRED {
464-
assert.EqualValues(t, 0, accountInfo.Balance)
465-
} else if tc.expectedBalanceSource == accountpb.TokenAccountInfo_BALANCE_SOURCE_CACHE {
466-
assert.EqualValues(t, tc.balance, accountInfo.Balance)
467-
} else {
468-
assert.EqualValues(t, 0, accountInfo.Balance)
469-
}
469+
accountInfo, ok := resp.TokenAccountInfos[timelockAccounts.Vault.PublicKey().ToBase58()]
470+
require.True(t, ok)
470471

471-
assert.Equal(t, tc.expectedManagementState, accountInfo.ManagementState)
472-
assert.Equal(t, tc.expectedBlockchainState, accountInfo.BlockchainState)
473-
assert.Equal(t, tc.expectedClaimState, accountInfo.ClaimState)
472+
assert.Equal(t, commonpb.AccountType_REMOTE_SEND_GIFT_CARD, accountInfo.AccountType)
473+
assert.Equal(t, giftCardOwnerAccount.PublicKey().ToBytes(), accountInfo.Owner.Value)
474+
assert.Equal(t, giftCardOwnerAccount.PublicKey().ToBytes(), accountInfo.Authority.Value)
475+
assert.Equal(t, timelockAccounts.Vault.PublicKey().ToBytes(), accountInfo.Address.Value)
476+
assert.Equal(t, common.CoreMintAccount.PublicKey().ToBytes(), accountInfo.Mint.Value)
477+
assert.EqualValues(t, 0, accountInfo.Index)
474478

475-
require.NotNil(t, accountInfo.OriginalExchangeData)
476-
assert.EqualValues(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.ExchangeCurrency, accountInfo.OriginalExchangeData.Currency)
477-
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.ExchangeRate, accountInfo.OriginalExchangeData.ExchangeRate)
478-
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.NativeAmount, accountInfo.OriginalExchangeData.NativeAmount)
479-
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.Quantity, accountInfo.OriginalExchangeData.Quarks)
479+
assert.Equal(t, tc.expectedBalanceSource, accountInfo.BalanceSource)
480+
if tc.simulateClaimInCode || tc.simulateAutoReturnInCode || tc.expectedClaimState == accountpb.TokenAccountInfo_CLAIM_STATE_CLAIMED || tc.expectedClaimState == accountpb.TokenAccountInfo_CLAIM_STATE_EXPIRED {
481+
assert.EqualValues(t, 0, accountInfo.Balance)
482+
} else if tc.expectedBalanceSource == accountpb.TokenAccountInfo_BALANCE_SOURCE_CACHE {
483+
assert.EqualValues(t, tc.balance, accountInfo.Balance)
484+
} else {
485+
assert.EqualValues(t, 0, accountInfo.Balance)
486+
}
480487

481-
accountInfoRecord, err := env.data.GetLatestAccountInfoByOwnerAddressAndType(env.ctx, ownerAccount.PublicKey().ToBase58(), commonpb.AccountType_REMOTE_SEND_GIFT_CARD)
482-
require.NoError(t, err)
483-
assert.False(t, accountInfoRecord.RequiresDepositSync)
488+
assert.Equal(t, tc.expectedManagementState, accountInfo.ManagementState)
489+
assert.Equal(t, tc.expectedBlockchainState, accountInfo.BlockchainState)
490+
assert.Equal(t, tc.expectedClaimState, accountInfo.ClaimState)
491+
492+
require.NotNil(t, accountInfo.OriginalExchangeData)
493+
assert.EqualValues(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.ExchangeCurrency, accountInfo.OriginalExchangeData.Currency)
494+
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.ExchangeRate, accountInfo.OriginalExchangeData.ExchangeRate)
495+
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.NativeAmount, accountInfo.OriginalExchangeData.NativeAmount)
496+
assert.Equal(t, giftCardIssuedIntentRecord.SendPublicPaymentMetadata.Quantity, accountInfo.OriginalExchangeData.Quarks)
497+
498+
assert.Equal(t, requestingOwnerAccount != nil && requestingOwnerAccount == giftCardIssuerOwnerAccount, accountInfo.IsGiftCardIssuer)
499+
500+
accountInfoRecord, err := env.data.GetLatestAccountInfoByOwnerAddressAndType(env.ctx, giftCardOwnerAccount.PublicKey().ToBase58(), commonpb.AccountType_REMOTE_SEND_GIFT_CARD)
501+
require.NoError(t, err)
502+
assert.False(t, accountInfoRecord.RequiresDepositSync)
503+
}
484504
}
485505
}
486506

@@ -618,7 +638,7 @@ func TestUnauthenticatedRPC(t *testing.T) {
618638
defer cleanup()
619639

620640
ownerAccount := testutil.NewRandomAccount(t)
621-
// swapAuthorityAccount := testutil.NewRandomAccount(t)
641+
requestingOwnerAccount := testutil.NewRandomAccount(t)
622642
maliciousAccount := testutil.NewRandomAccount(t)
623643

624644
isCodeAccountReq := &accountpb.IsCodeAccountRequest{
@@ -644,6 +664,37 @@ func TestUnauthenticatedRPC(t *testing.T) {
644664

645665
_, err = env.client.GetTokenAccountInfos(env.ctx, getTokenAccountInfosReq)
646666
testutil.AssertStatusErrorWithCode(t, err, codes.Unauthenticated)
667+
668+
getTokenAccountInfosReq = &accountpb.GetTokenAccountInfosRequest{
669+
Owner: ownerAccount.ToProto(),
670+
RequestingOwner: requestingOwnerAccount.ToProto(),
671+
}
672+
reqBytes, err = proto.Marshal(getTokenAccountInfosReq)
673+
require.NoError(t, err)
674+
getTokenAccountInfosReq.Signature = &commonpb.Signature{
675+
Value: ed25519.Sign(ownerAccount.PrivateKey().ToBytes(), reqBytes),
676+
}
677+
678+
_, err = env.client.GetTokenAccountInfos(env.ctx, getTokenAccountInfosReq)
679+
testutil.AssertStatusErrorWithCode(t, err, codes.Unauthenticated)
680+
681+
getTokenAccountInfosReq = &accountpb.GetTokenAccountInfosRequest{
682+
Owner: ownerAccount.ToProto(),
683+
RequestingOwner: requestingOwnerAccount.ToProto(),
684+
}
685+
reqBytes, err = proto.Marshal(getTokenAccountInfosReq)
686+
require.NoError(t, err)
687+
sig1 := &commonpb.Signature{
688+
Value: ed25519.Sign(ownerAccount.PrivateKey().ToBytes(), reqBytes),
689+
}
690+
sig2 := &commonpb.Signature{
691+
Value: ed25519.Sign(maliciousAccount.PrivateKey().ToBytes(), reqBytes),
692+
}
693+
getTokenAccountInfosReq.Signature = sig1
694+
getTokenAccountInfosReq.RequestingOwnerSignature = sig2
695+
696+
_, err = env.client.GetTokenAccountInfos(env.ctx, getTokenAccountInfosReq)
697+
testutil.AssertStatusErrorWithCode(t, err, codes.Unauthenticated)
647698
}
648699

649700
func setupAccountRecords(t *testing.T, env testEnv, ownerAccount, authorityAccount *common.Account, index uint64, accountType commonpb.AccountType) *common.AccountRecords {
@@ -722,18 +773,3 @@ func setupCachedBalance(t *testing.T, env testEnv, accountRecords *common.Accoun
722773
}
723774
require.NoError(t, env.data.SaveExternalDeposit(env.ctx, depositRecord))
724775
}
725-
726-
func setupOpenAccountsIntent(t *testing.T, env testEnv, ownerAccount *common.Account) {
727-
intentRecord := &intent.Record{
728-
IntentId: testutil.NewRandomAccount(t).PublicKey().ToBase58(),
729-
IntentType: intent.OpenAccounts,
730-
731-
InitiatorOwnerAccount: ownerAccount.PublicKey().ToBase58(),
732-
733-
OpenAccountsMetadata: &intent.OpenAccountsMetadata{},
734-
735-
State: intent.StatePending,
736-
}
737-
738-
require.NoError(t, env.data.SaveIntent(env.ctx, intentRecord))
739-
}

0 commit comments

Comments
 (0)