diff --git a/pkg/code/server/transaction/action_handler.go b/pkg/code/server/transaction/action_handler.go index f9e6987a..4b029ca7 100644 --- a/pkg/code/server/transaction/action_handler.go +++ b/pkg/code/server/transaction/action_handler.go @@ -43,26 +43,8 @@ type newFulfillmentMetadata struct { disableActiveScheduling bool } -// BaseActionHandler is a base interface for operation-specific action handlers -// -// Note: Action handlers should load all required state on initialization to -// avoid duplicated work across interface method calls. -type BaseActionHandler interface { - // GetServerParameter gets the server parameter for the action within the context - // of the intent. - GetServerParameter() *transactionpb.ServerParameter - - // OnSaveToDB is a callback when the action is being saved to the DB - // within the scope of a DB transaction. Additional supporting DB records - // (ie. not the action or fulfillment records) relevant to the action should - // be saved here. - OnSaveToDB(ctx context.Context) error -} - // CreateActionHandler is an interface for creating new actions type CreateActionHandler interface { - BaseActionHandler - // FulfillmentCount returns the total number of fulfillments that // will be created for the action. FulfillmentCount() int @@ -70,6 +52,10 @@ type CreateActionHandler interface { // PopulateMetadata populates action metadata into the provided record PopulateMetadata(actionRecord *action.Record) error + // GetServerParameter gets the server parameter for the action within the context + // of the intent. + GetServerParameter() *transactionpb.ServerParameter + // RequiresNonce determines whether a nonce should be acquired for the // fulfillment being created. This should be true whenever a virtual // instruction needs to be signed by the client. @@ -81,22 +67,12 @@ type CreateActionHandler interface { nonce *common.Account, bh solana.Blockhash, ) (*newFulfillmentMetadata, error) -} - -// UpgradeActionHandler is an interface for upgrading existing actions. It's -// assumed we'll only be upgrading a single fulfillment. -type UpgradeActionHandler interface { - BaseActionHandler - // GetFulfillmentBeingUpgraded gets the original fulfillment that's being - // upgraded. - GetFulfillmentBeingUpgraded() *fulfillment.Record - - // GetFulfillmentMetadata gets upgraded fulfillment metadata - GetFulfillmentMetadata( - nonce *common.Account, - bh solana.Blockhash, - ) (*newFulfillmentMetadata, error) + // OnSaveToDB is a callback when the action is being saved to the DB + // within the scope of a DB transaction. Additional supporting DB records + // (ie. not the action or fulfillment records) relevant to the action should + // be saved here. + OnSaveToDB(ctx context.Context) error } type OpenAccountActionHandler struct { diff --git a/pkg/code/server/transaction/intent.go b/pkg/code/server/transaction/intent.go index 6e8a0c5b..cb77a635 100644 --- a/pkg/code/server/transaction/intent.go +++ b/pkg/code/server/transaction/intent.go @@ -27,7 +27,6 @@ import ( "github.com/code-payments/code-server/pkg/code/data/intent" "github.com/code-payments/code-server/pkg/code/data/nonce" "github.com/code-payments/code-server/pkg/code/data/timelock" - "github.com/code-payments/code-server/pkg/code/data/webhook" "github.com/code-payments/code-server/pkg/code/transaction" "github.com/code-payments/code-server/pkg/grpc/client" "github.com/code-payments/code-server/pkg/pointer" @@ -90,7 +89,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm } // Figure out what kind of intent we're operating on and initialize the intent handler - var intentHandler interface{} + var intentHandler CreateIntentHandler var intentHasNewOwner bool // todo: intent handler should specify this switch submitActionsReq.Metadata.Type.(type) { case *transactionpb.Metadata_OpenAccounts: @@ -107,12 +106,6 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm return handleSubmitIntentError(streamer, status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions.Metadata is nil")) } - var isIntentUpdateOperation bool - switch intentHandler.(type) { - case UpdateIntentHandler: - isIntentUpdateOperation = true - } - // The public key that is the owner and signed the intent. This may not be // the user depending upon the context of how the user initiated the intent. submitActionsOwnerAccount, err := common.NewAccountFromProto(submitActionsReq.Owner) @@ -237,100 +230,58 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm return handleSubmitIntentError(streamer, err) } - if isIntentUpdateOperation { - // Intent is an update, so ensure we have an existing DB record - if existingIntentRecord == nil { - return handleSubmitIntentError(streamer, newIntentValidationError("intent doesn't exists")) - } - - // Intent is an update, so ensure the original owner account is operating - // on the intent - if initiatorOwnerAccount.PublicKey().ToBase58() != existingIntentRecord.InitiatorOwnerAccount { - return handleSubmitIntentError(streamer, status.Error(codes.PermissionDenied, "")) - } - - // Validate the update with intent-specific logic - err = intentHandler.(UpdateIntentHandler).AllowUpdate(ctx, existingIntentRecord, submitActionsReq.Metadata, submitActionsReq.Actions) - if err != nil { - switch err.(type) { - case IntentValidationError: - log.WithError(err).Warn("intent update failed validation") - case IntentDeniedError: - log.WithError(err).Warn("intent update was denied") - case StaleStateError: - log.WithError(err).Warn("detected a client with stale state") - default: - log.WithError(err).Warn("failure checking if intent update was allowed") - } - return handleSubmitIntentError(streamer, err) - } - - // Use the existing DB record going forward - intentRecord = existingIntentRecord - } else { - // We're operating on a new intent, so validate we don't have an existing DB record - if existingIntentRecord != nil { - log.Warn("client is attempting to resubmit an intent or reuse an intent id") - return handleSubmitIntentError(streamer, newStaleStateError("intent already exists")) - } - - createIntentHandler := intentHandler.(CreateIntentHandler) - - // Populate metadata into the new DB record - err = createIntentHandler.PopulateMetadata(ctx, intentRecord, submitActionsReq.Metadata) - if err != nil { - log.WithError(err).Warn("failure populating intent metadata") - return handleSubmitIntentError(streamer, err) - } + // We're operating on a new intent, so validate we don't have an existing DB record + if existingIntentRecord != nil { + log.Warn("client is attempting to resubmit an intent or reuse an intent id") + return handleSubmitIntentError(streamer, newStaleStateError("intent already exists")) + } - isNoop, err := createIntentHandler.IsNoop(ctx, intentRecord, submitActionsReq.Metadata, submitActionsReq.Actions) - if err != nil { - log.WithError(err).Warn("failure checking if intent is a no-op") - return handleSubmitIntentError(streamer, err) - } else if isNoop { - if err := streamer.Send(okResp); err != nil { - return handleSubmitIntentError(streamer, err) - } - return nil - } + // Populate metadata into the new DB record + err = intentHandler.PopulateMetadata(ctx, intentRecord, submitActionsReq.Metadata) + if err != nil { + log.WithError(err).Warn("failure populating intent metadata") + return handleSubmitIntentError(streamer, err) + } - // Distributed locking on additional accounts possibly not known until - // populating intent metadata. Importantly, this must be done prior to - // doing validation checks in AllowCreation. - additionalAccountsToLock, err := createIntentHandler.GetAdditionalAccountsToLock(ctx, intentRecord) - if err != nil { + isNoop, err := intentHandler.IsNoop(ctx, intentRecord, submitActionsReq.Metadata, submitActionsReq.Actions) + if err != nil { + log.WithError(err).Warn("failure checking if intent is a no-op") + return handleSubmitIntentError(streamer, err) + } else if isNoop { + if err := streamer.Send(okResp); err != nil { return handleSubmitIntentError(streamer, err) } + return nil + } - if additionalAccountsToLock.DestinationOwner != nil { - destinationOwnerLock := s.ownerLocks.Get(additionalAccountsToLock.DestinationOwner.PublicKey().ToBytes()) - if destinationOwnerLock != initiatorOwnerLock { // Because we're using striped locks - destinationOwnerLock.Lock() - defer destinationOwnerLock.Unlock() - } - } + // Distributed locking on additional accounts possibly not known until + // populating intent metadata. Importantly, this must be done prior to + // doing validation checks in AllowCreation. + additionalAccountsToLock, err := intentHandler.GetAdditionalAccountsToLock(ctx, intentRecord) + if err != nil { + return handleSubmitIntentError(streamer, err) + } - if additionalAccountsToLock.RemoteSendGiftCardVault != nil { - giftCardLock := s.giftCardLocks.Get(additionalAccountsToLock.RemoteSendGiftCardVault.PublicKey().ToBytes()) - giftCardLock.Lock() - defer giftCardLock.Unlock() - } + if additionalAccountsToLock.RemoteSendGiftCardVault != nil { + giftCardLock := s.giftCardLocks.Get(additionalAccountsToLock.RemoteSendGiftCardVault.PublicKey().ToBytes()) + giftCardLock.Lock() + defer giftCardLock.Unlock() + } - // Validate the new intent with intent-specific logic - err = createIntentHandler.AllowCreation(ctx, intentRecord, submitActionsReq.Metadata, submitActionsReq.Actions) - if err != nil { - switch err.(type) { - case IntentValidationError: - log.WithError(err).Warn("new intent failed validation") - case IntentDeniedError: - log.WithError(err).Warn("new intent was denied") - case StaleStateError: - log.WithError(err).Warn("detected a client with stale state") - default: - log.WithError(err).Warn("failure checking if new intent was allowed") - } - return handleSubmitIntentError(streamer, err) + // Validate the new intent with intent-specific logic + err = intentHandler.AllowCreation(ctx, intentRecord, submitActionsReq.Metadata, submitActionsReq.Actions) + if err != nil { + switch err.(type) { + case IntentValidationError: + log.WithError(err).Warn("new intent failed validation") + case IntentDeniedError: + log.WithError(err).Warn("new intent was denied") + case StaleStateError: + log.WithError(err).Warn("detected a client with stale state") + default: + log.WithError(err).Warn("failure checking if new intent was allowed") } + return handleSubmitIntentError(streamer, err) } type fulfillmentWithSigningMetadata struct { @@ -344,7 +295,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm } // Convert all actions into a set of fulfillments - var actionHandlers []BaseActionHandler + var actionHandlers []CreateActionHandler var actionRecords []*action.Record var fulfillments []fulfillmentWithSigningMetadata var reservedNonces []*transaction.SelectedNonce @@ -354,7 +305,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm // Figure out what kind of action we're operating on and initialize the // action handler - var actionHandler BaseActionHandler + var actionHandler CreateActionHandler var actionType action.Type switch typed := protoAction.Type.(type) { case *transactionpb.Action_OpenAccount: @@ -381,128 +332,74 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm return handleSubmitIntentError(streamer, errors.New("error initializing action handler")) } - var isUpgradeActionOperation bool - switch actionHandler.(type) { - case UpgradeActionHandler: - isUpgradeActionOperation = true - } - - // Updates equate to only upgrading existing fulfillments, and vice versa, for now. - if isUpgradeActionOperation != isIntentUpdateOperation { - // If we hit this, then we've failed somewhere in the validation code. - log.Warn("intent update status != action upgrade status") - return handleSubmitIntentError(streamer, errors.New("intent update status != action upgrade status")) - } - actionHandlers = append(actionHandlers, actionHandler) - // Upgrades cannot create new actions. - if !isUpgradeActionOperation { - // Construct the equivalent action record - actionRecord := &action.Record{ - Intent: intentRecord.IntentId, - IntentType: intentRecord.IntentType, - - ActionId: protoAction.Id, - ActionType: actionType, + // Construct the equivalent action record + actionRecord := &action.Record{ + Intent: intentRecord.IntentId, + IntentType: intentRecord.IntentType, - State: action.StateUnknown, - } + ActionId: protoAction.Id, + ActionType: actionType, - err := actionHandler.(CreateActionHandler).PopulateMetadata(actionRecord) - if err != nil { - log.WithError(err).Warn("failure populating action metadata") - return handleSubmitIntentError(streamer, err) - } + State: action.StateUnknown, + } - actionRecords = append(actionRecords, actionRecord) + err := actionHandler.PopulateMetadata(actionRecord) + if err != nil { + log.WithError(err).Warn("failure populating action metadata") + return handleSubmitIntentError(streamer, err) } + actionRecords = append(actionRecords, actionRecord) + // Get action-specific server parameters needed by client to construct the transaction serverParameter := actionHandler.GetServerParameter() serverParameter.ActionId = protoAction.Id serverParameters = append(serverParameters, serverParameter) - fulfillmentCount := 1 - if !isUpgradeActionOperation { - fulfillmentCount = actionHandler.(CreateActionHandler).FulfillmentCount() - } + fulfillmentCount := actionHandler.FulfillmentCount() for j := 0; j < fulfillmentCount; j++ { var newFulfillmentMetadata *newFulfillmentMetadata - var selectedNonce *transaction.SelectedNonce var actionId uint32 - if isUpgradeActionOperation { - upgradeActionHandler := actionHandler.(UpgradeActionHandler) - - // Find the fulfillment that is being upgraded. - fulfillmentToUpgrade := upgradeActionHandler.GetFulfillmentBeingUpgraded() - if fulfillmentToUpgrade.State != fulfillment.StateUnknown { - log.Warn("fulfillment being upgraded isn't in the unknown state") - return handleSubmitIntentError(streamer, errors.New("invalid fulfillment to upgrade")) - } - - // Re-use the same nonce as the one in the fulfillment we're upgrading, - // so we avoid server from submitting both. - selectedNonce, err = transaction.SelectVirtualNonceFromFulfillmentToUpgrade(ctx, s.data, fulfillmentToUpgrade) - if err != nil { - log.WithError(err).Warn("failure selecting nonce from existing fulfillment") - return handleSubmitIntentError(streamer, err) - } - defer selectedNonce.Unlock() - // Get metadata for the fulfillment being upgraded - newFulfillmentMetadata, err = upgradeActionHandler.GetFulfillmentMetadata( - selectedNonce.Account, - selectedNonce.Blockhash, - ) - if err != nil { - log.WithError(err).Warn("failure getting fulfillment metadata") - return handleSubmitIntentError(streamer, err) - } - - actionId = fulfillmentToUpgrade.ActionId - } else { - createActionHandler := actionHandler.(CreateActionHandler) - - // Select any available nonce reserved for use for a client transaction, - // if it's required - var nonceAccount *common.Account - var nonceBlockchash solana.Blockhash - if createActionHandler.RequiresNonce(j) { - selectedNonce, err = transaction.SelectAvailableNonce(ctx, s.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction) - if err != nil { - log.WithError(err).Warn("failure selecting available nonce") - return handleSubmitIntentError(streamer, err) - } - defer func() { - // If we never assign the nonce a signature in the action creation flow, - // it's safe to put it back in the available pool. The client will have - // caused a failed RPC call, and we want to avoid malicious or erroneous - // clients from consuming our nonce pool! - selectedNonce.ReleaseIfNotReserved() - selectedNonce.Unlock() - }() - nonceAccount = selectedNonce.Account - nonceBlockchash = selectedNonce.Blockhash - } else { - selectedNonce = nil - } - - // Get metadata for the new fulfillment being created - newFulfillmentMetadata, err = createActionHandler.GetFulfillmentMetadata( - j, - nonceAccount, - nonceBlockchash, - ) + // Select any available nonce reserved for use for a client transaction, + // if it's required + var selectedNonce *transaction.SelectedNonce + var nonceAccount *common.Account + var nonceBlockchash solana.Blockhash + if actionHandler.RequiresNonce(j) { + selectedNonce, err = transaction.SelectAvailableNonce(ctx, s.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction) if err != nil { - log.WithError(err).Warn("failure getting fulfillment metadata") + log.WithError(err).Warn("failure selecting available nonce") return handleSubmitIntentError(streamer, err) } + defer func() { + // If we never assign the nonce a signature in the action creation flow, + // it's safe to put it back in the available pool. The client will have + // caused a failed RPC call, and we want to avoid malicious or erroneous + // clients from consuming our nonce pool! + selectedNonce.ReleaseIfNotReserved() + selectedNonce.Unlock() + }() + nonceAccount = selectedNonce.Account + nonceBlockchash = selectedNonce.Blockhash + } - actionId = protoAction.Id + // Get metadata for the new fulfillment being created + newFulfillmentMetadata, err = actionHandler.GetFulfillmentMetadata( + j, + nonceAccount, + nonceBlockchash, + ) + if err != nil { + log.WithError(err).Warn("failure getting fulfillment metadata") + return handleSubmitIntentError(streamer, err) } + actionId = protoAction.Id + // Construct the fulfillment record fulfillmentRecord := &fulfillment.Record{ Intent: intentRecord.IntentId, @@ -575,9 +472,6 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm } metricsIntentTypeValue := intentRecord.IntentType.String() - if isIntentUpdateOperation { - metricsIntentTypeValue = "upgrade_privacy" - } latencyBeforeSignatureSubmission := time.Since(start) recordSubmitIntentLatencyBreakdownEvent( ctx, @@ -653,21 +547,18 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm // operation. Not all store implementations have real support for this, so // if anything is added, then ensure it does! err = s.data.ExecuteInTx(ctx, sql.LevelDefault, func(ctx context.Context) error { - // Updates cannot create new intent or action records, yet. - if !isIntentUpdateOperation { - // Save the intent record - err = s.data.SaveIntent(ctx, intentRecord) - if err != nil { - log.WithError(err).Warn("failure saving intent record") - return err - } + // Save the intent record + err = s.data.SaveIntent(ctx, intentRecord) + if err != nil { + log.WithError(err).Warn("failure saving intent record") + return err + } - // Save all actions - err = s.data.PutAllActions(ctx, actionRecords...) - if err != nil { - log.WithError(err).Warn("failure saving action records") - return err - } + // Save all actions + err = s.data.PutAllActions(ctx, actionRecords...) + if err != nil { + log.WithError(err).Warn("failure saving action records") + return err } // Save all fulfillment records @@ -682,11 +573,8 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm // Reserve the nonce with the latest server-signed fulfillment. if fulfillmentWithMetadata.requiresClientSignature { nonceToReserve := reservedNonces[i] - if isIntentUpdateOperation { - err = nonceToReserve.UpdateSignature(ctx, *fulfillmentWithMetadata.record.VirtualSignature) - } else { - err = nonceToReserve.MarkReservedWithSignature(ctx, *fulfillmentWithMetadata.record.VirtualSignature) - } + + err = nonceToReserve.MarkReservedWithSignature(ctx, *fulfillmentWithMetadata.record.VirtualSignature) if err != nil { log.WithError(err).Warn("failure reserving nonce with fulfillment signature") return err @@ -708,53 +596,18 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm } } - // Updates apply to intents that have already been created, and consequently, - // been previously moved to the pending state. - if !isIntentUpdateOperation { - // Save additional state related to the intent - err = intentHandler.(CreateIntentHandler).OnSaveToDB(ctx, intentRecord) - if err != nil { - log.WithError(err).Warn("failure executing intent db save callback") - return err - } - - // Mark the intent as pending once everything else has succeeded - err = s.markIntentAsPending(ctx, intentRecord) - if err != nil { - log.WithError(err).Warn("failure marking the intent as pending") - return err - } - - // Mark the associated webhook as pending, if it was registered - /* - err = s.markWebhookAsPending(ctx, intentRecord.IntentId) - if err != nil { - log.WithError(err).Warn("failure marking webhook as pending") - return err - } - */ + // Save additional state related to the intent + err = intentHandler.OnSaveToDB(ctx, intentRecord) + if err != nil { + log.WithError(err).Warn("failure executing intent db save callback") + return err + } - // Create a message on the intent ID to indicate the intent was submitted - // - // Note: This function only errors on the DB save, and not forwarding, which - // is ideal for this use case. - // - // todo: We could also make this an account update event by creating the message - // on each involved owner accounts' stream. - /* - _, err = s.messagingClient.InternallyCreateMessage(ctx, rendezvousKey, &messagingpb.Message{ - Kind: &messagingpb.Message_IntentSubmitted{ - IntentSubmitted: &messagingpb.IntentSubmitted{ - IntentId: submitActionsReq.Id, - Metadata: submitActionsReq.Metadata, - }, - }, - }) - if err != nil { - log.WithError(err).Warn("failure creating intent submitted message") - return err - } - */ + // Mark the intent as pending once everything else has succeeded + err = s.markIntentAsPending(ctx, intentRecord) + if err != nil { + log.WithError(err).Warn("failure marking the intent as pending") + return err } return nil @@ -772,17 +625,14 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm log.Debug("intent submitted") // Post-processing when an intent has been committed to the DB. - if !isIntentUpdateOperation { - err = intentHandler.(CreateIntentHandler).OnCommittedToDB(ctx, intentRecord) - if err != nil { - log.WithError(err).Warn("failure executing intent committed callback handler handler") - } + + err = intentHandler.OnCommittedToDB(ctx, intentRecord) + if err != nil { + log.WithError(err).Warn("failure executing intent committed callback handler handler") } // Fire off some success metrics - if !isIntentUpdateOperation { - recordUserIntentCreatedEvent(ctx, intentRecord) - } + recordUserIntentCreatedEvent(ctx, intentRecord) latencyAfterSignatureSubmission := time.Since(tsAfterSignatureSubmission) recordSubmitIntentLatencyBreakdownEvent( @@ -837,23 +687,6 @@ func (s *transactionServer) markIntentAsPending(ctx context.Context, record *int return s.data.SaveIntent(ctx, record) } -func (s *transactionServer) markWebhookAsPending(ctx context.Context, id string) error { - webhookRecord, err := s.data.GetWebhook(ctx, id) - if err == webhook.ErrNotFound { - return nil - } else if err != nil { - return err - } - - if webhookRecord.State != webhook.StateUnknown { - return nil - } - - webhookRecord.NextAttemptAt = pointer.Time(time.Now()) - webhookRecord.State = webhook.StatePending - return s.data.UpdateWebhook(ctx, webhookRecord) -} - func (s *transactionServer) GetIntentMetadata(ctx context.Context, req *transactionpb.GetIntentMetadataRequest) (*transactionpb.GetIntentMetadataResponse, error) { intentId := base58.Encode(req.IntentId.Value) diff --git a/pkg/code/server/transaction/intent_handler.go b/pkg/code/server/transaction/intent_handler.go index d42199e2..6a25b17d 100644 --- a/pkg/code/server/transaction/intent_handler.go +++ b/pkg/code/server/transaction/intent_handler.go @@ -32,7 +32,6 @@ var accountTypesToOpen = []commonpb.AccountType{ } type lockableAccounts struct { - DestinationOwner *common.Account RemoteSendGiftCardVault *common.Account } @@ -76,12 +75,6 @@ type CreateIntentHandler interface { OnCommittedToDB(ctx context.Context, intentRecord *intent.Record) error } -// UpdateIntentHandler is an interface for handling updates to an existing intent -type UpdateIntentHandler interface { - // AllowUpdate determines whether an intent update should be allowed. - AllowUpdate(ctx context.Context, existingIntent *intent.Record, metdata *transactionpb.Metadata, actions []*transactionpb.Action) error -} - type OpenAccountsIntentHandler struct { conf *conf data code_data.Provider @@ -329,17 +322,17 @@ func (h *SendPublicPaymentIntentHandler) IsNoop(ctx context.Context, intentRecor } func (h *SendPublicPaymentIntentHandler) GetAdditionalAccountsToLock(ctx context.Context, intentRecord *intent.Record) (*lockableAccounts, error) { - if len(intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount) == 0 { + if !intentRecord.SendPublicPaymentMetadata.IsRemoteSend { return &lockableAccounts{}, nil } - destinationOwnerAccount, err := common.NewAccountFromPublicKeyString(intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount) + giftCardVaultAccount, err := common.NewAccountFromPublicKeyString(intentRecord.SendPublicPaymentMetadata.DestinationTokenAccount) if err != nil { return nil, err } return &lockableAccounts{ - DestinationOwner: destinationOwnerAccount, + RemoteSendGiftCardVault: giftCardVaultAccount, }, nil } diff --git a/pkg/code/transaction/nonce.go b/pkg/code/transaction/nonce.go index 7a1bfa86..50710fae 100644 --- a/pkg/code/transaction/nonce.go +++ b/pkg/code/transaction/nonce.go @@ -10,7 +10,6 @@ import ( "github.com/code-payments/code-server/pkg/code/common" code_data "github.com/code-payments/code-server/pkg/code/data" - "github.com/code-payments/code-server/pkg/code/data/fulfillment" "github.com/code-payments/code-server/pkg/code/data/nonce" "github.com/code-payments/code-server/pkg/retry" "github.com/code-payments/code-server/pkg/solana" @@ -126,63 +125,6 @@ func SelectAvailableNonce(ctx context.Context, data code_data.Provider, env nonc }, nil } -// SelectVirtualNonceFromFulfillmentToUpgrade selects a nonce from a fulfillment that -// is going to be upgraded. -func SelectVirtualNonceFromFulfillmentToUpgrade(ctx context.Context, data code_data.Provider, fulfillmentRecord *fulfillment.Record) (*SelectedNonce, error) { - if fulfillmentRecord.State != fulfillment.StateUnknown || fulfillmentRecord.Signature != nil { - return nil, errors.New("dangerous nonce selection from fulfillment") - } - - if fulfillmentRecord.VirtualNonce == nil { - return nil, errors.New("fulfillment doesn't have an assigned virtual nonce") - } - - lock := getNonceLock(*fulfillmentRecord.VirtualNonce) - lock.Lock() - - // Fetch after locking to get most up-to-date state - nonceRecord, err := data.GetNonce(ctx, *fulfillmentRecord.VirtualNonce) - if err != nil { - lock.Unlock() - return nil, err - } - - if nonceRecord.State != nonce.StateReserved { - lock.Unlock() - return nil, errors.New("virtual nonce isn't reserved") - } - - if nonceRecord.Blockhash != *fulfillmentRecord.VirtualBlockhash { - lock.Unlock() - return nil, errors.New("fulfillment record doesn't have the right virtual blockhash") - } - - if nonceRecord.Signature != *fulfillmentRecord.VirtualSignature { - lock.Unlock() - return nil, errors.New("virtual nonce isn't mapped to selected fulfillment") - } - - account, err := common.NewAccountFromPublicKeyString(nonceRecord.Address) - if err != nil { - lock.Unlock() - return nil, err - } - - bh, err := base58.Decode(*fulfillmentRecord.VirtualBlockhash) - if err != nil { - lock.Unlock() - return nil, err - } - - return &SelectedNonce{ - distributedLock: lock, - data: data, - record: nonceRecord, - Account: account, - Blockhash: solana.Blockhash(bh), - }, nil -} - // MarkReservedWithSignature marks the nonce as reserved with a signature func (n *SelectedNonce) MarkReservedWithSignature(ctx context.Context, sig string) error { if len(sig) == 0 { @@ -219,33 +161,6 @@ func (n *SelectedNonce) MarkReservedWithSignature(ctx context.Context, sig strin return n.data.SaveNonce(ctx, n.record) } -// UpdateSignature updates the signature for a reserved nonce. The use case here -// being transactions that share a nonce, and the new transaction being designated -// as the one to submit to the blockchain. -func (n *SelectedNonce) UpdateSignature(ctx context.Context, sig string) error { - if len(sig) == 0 { - return errors.New("signature is empty") - } - - n.localLock.Lock() - defer n.localLock.Unlock() - - if n.isUnlocked { - return errors.New("nonce is unlocked") - } - - if n.record.Signature == sig { - return nil - } - - if n.record.State != nonce.StateReserved { - return errors.New("nonce must be in a reserved state") - } - - n.record.Signature = sig - return n.data.SaveNonce(ctx, n.record) -} - // ReleaseIfNotReserved makes a nonce available if it hasn't been reserved with // a signature. It's recommended to call this in tandem with Unlock when the // caller knows it's safe to go from the reserved to available state (ie. don't diff --git a/pkg/code/transaction/nonce_test.go b/pkg/code/transaction/nonce_test.go index 8c799699..766ea9eb 100644 --- a/pkg/code/transaction/nonce_test.go +++ b/pkg/code/transaction/nonce_test.go @@ -12,10 +12,8 @@ import ( "github.com/code-payments/code-server/pkg/code/common" code_data "github.com/code-payments/code-server/pkg/code/data" - "github.com/code-payments/code-server/pkg/code/data/fulfillment" "github.com/code-payments/code-server/pkg/code/data/nonce" "github.com/code-payments/code-server/pkg/code/data/vault" - "github.com/code-payments/code-server/pkg/pointer" "github.com/code-payments/code-server/pkg/solana" "github.com/code-payments/code-server/pkg/testutil" ) @@ -66,86 +64,6 @@ func TestNonce_SelectAvailableNonce(t *testing.T) { } -func TestNonce_SelectNonceFromFulfillmentToUpgrade_HappyPath(t *testing.T) { - env := setupNonceTestEnv(t) - - generateAvailableNonces(t, env, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction, 2) - - selectedNonce, err := SelectAvailableNonce(env.ctx, env.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction) - require.NoError(t, err) - - fulfillmentToUpgrade := &fulfillment.Record{ - VirtualNonce: pointer.String(selectedNonce.Account.PublicKey().ToBase58()), - VirtualBlockhash: pointer.String(base58.Encode(selectedNonce.Blockhash[:])), - VirtualSignature: pointer.String("signature"), - } - - require.NoError(t, selectedNonce.MarkReservedWithSignature(env.ctx, *fulfillmentToUpgrade.VirtualSignature)) - - selectedNonce.Unlock() - - selectedNonce, err = SelectVirtualNonceFromFulfillmentToUpgrade(env.ctx, env.data, fulfillmentToUpgrade) - require.NoError(t, err) - - assert.Equal(t, *fulfillmentToUpgrade.VirtualNonce, selectedNonce.Account.PublicKey().ToBase58()) - assert.Equal(t, *fulfillmentToUpgrade.VirtualBlockhash, base58.Encode(selectedNonce.Blockhash[:])) - - require.NoError(t, selectedNonce.UpdateSignature(env.ctx, "new_signature")) - - updatedRecord, err := env.data.GetNonce(env.ctx, selectedNonce.Account.PublicKey().ToBase58()) - require.NoError(t, err) - assert.Equal(t, nonce.StateReserved, updatedRecord.State) - assert.Equal(t, "new_signature", updatedRecord.Signature) - - selectedNonce.Unlock() - - _, err = SelectVirtualNonceFromFulfillmentToUpgrade(env.ctx, env.data, fulfillmentToUpgrade) - assert.Error(t, err) -} - -func TestNonce_SelectVirtualNonceFromFulfillmentToUpgrade_DangerousPath(t *testing.T) { - env := setupNonceTestEnv(t) - - generateAvailableNonces(t, env, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction, 2) - - selectedNonce, err := SelectAvailableNonce(env.ctx, env.data, nonce.EnvironmentCvm, common.CodeVmAccount.PublicKey().ToBase58(), nonce.PurposeClientTransaction) - require.NoError(t, err) - - fulfillmentToUpgrade := &fulfillment.Record{ - VirtualNonce: pointer.String(selectedNonce.Account.PublicKey().ToBase58()), - VirtualBlockhash: pointer.String(base58.Encode(selectedNonce.Blockhash[:])), - VirtualSignature: pointer.String("signature"), - } - - require.NoError(t, selectedNonce.MarkReservedWithSignature(env.ctx, *fulfillmentToUpgrade.VirtualSignature)) - - selectedNonce.Unlock() - - nonceRecord, err := env.data.GetNonce(env.ctx, selectedNonce.Account.PublicKey().ToBase58()) - require.NoError(t, err) - - originalBlockhash := nonceRecord.Blockhash - nonceRecord.Blockhash = "Cmui8pHYbKKox8g7n7xa2Qaxh1TSJsHpr3xCeNaEisdy" - require.NoError(t, env.data.SaveNonce(env.ctx, nonceRecord)) - - _, err = SelectVirtualNonceFromFulfillmentToUpgrade(env.ctx, env.data, fulfillmentToUpgrade) - assert.Error(t, err) - - nonceRecord.Blockhash = originalBlockhash - nonceRecord.State = nonce.StateAvailable - require.NoError(t, env.data.SaveNonce(env.ctx, nonceRecord)) - - _, err = SelectVirtualNonceFromFulfillmentToUpgrade(env.ctx, env.data, fulfillmentToUpgrade) - assert.Error(t, err) - - nonceRecord.State = nonce.StateReserved - nonceRecord.Signature = "other_signature" - require.NoError(t, env.data.SaveNonce(env.ctx, nonceRecord)) - - _, err = SelectVirtualNonceFromFulfillmentToUpgrade(env.ctx, env.data, fulfillmentToUpgrade) - assert.Error(t, err) -} - func TestNonce_MarkReservedWithSignature(t *testing.T) { env := setupNonceTestEnv(t) @@ -165,25 +83,6 @@ func TestNonce_MarkReservedWithSignature(t *testing.T) { assert.Equal(t, "signature1", updatedRecord.Signature) } -func TestNonce_UpdateSignature(t *testing.T) { - env := setupNonceTestEnv(t) - - generateAvailableNonce(t, env, nonce.EnvironmentSolana, nonce.EnvironmentInstanceSolanaMainnet, nonce.PurposeClientTransaction) - - selectedNonce, err := SelectAvailableNonce(env.ctx, env.data, nonce.EnvironmentSolana, nonce.EnvironmentInstanceSolanaMainnet, nonce.PurposeClientTransaction) - require.NoError(t, err) - require.NoError(t, selectedNonce.MarkReservedWithSignature(env.ctx, "signature1")) - - assert.Error(t, selectedNonce.UpdateSignature(env.ctx, "")) - require.NoError(t, selectedNonce.UpdateSignature(env.ctx, "signature1")) - require.NoError(t, selectedNonce.UpdateSignature(env.ctx, "signature2")) - - updatedRecord, err := env.data.GetNonce(env.ctx, selectedNonce.Account.PublicKey().ToBase58()) - require.NoError(t, err) - assert.Equal(t, nonce.StateReserved, updatedRecord.State) - assert.Equal(t, "signature2", updatedRecord.Signature) -} - func TestNonce_ReleaseIfNotReserved(t *testing.T) { env := setupNonceTestEnv(t) @@ -194,8 +93,6 @@ func TestNonce_ReleaseIfNotReserved(t *testing.T) { require.NoError(t, selectedNonce.ReleaseIfNotReserved()) - assert.Error(t, selectedNonce.UpdateSignature(env.ctx, "signature")) - updatedRecord, err := env.data.GetNonce(env.ctx, selectedNonce.Account.PublicKey().ToBase58()) require.NoError(t, err) assert.Equal(t, nonce.StateAvailable, updatedRecord.State)