Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.23.0
require (
github.com/aws/aws-sdk-go-v2 v0.17.0
github.com/bits-and-blooms/bloom/v3 v3.1.0
github.com/code-payments/code-protobuf-api v1.19.1-0.20250602171721-c057e3310d81
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba
github.com/emirpasic/gods v1.12.0
github.com/envoyproxy/protoc-gen-validate v1.2.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/code-payments/code-protobuf-api v1.19.1-0.20250602171721-c057e3310d81 h1:sX2VijdiCok3hJI1Yv/a+NslvVzCTolpNDQIOFzk9DE=
github.com/code-payments/code-protobuf-api v1.19.1-0.20250602171721-c057e3310d81/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052 h1:lfxaakPHAWFPukrqsUn8nYdpw1WaXQfP4KLCzmL8UxU=
github.com/code-payments/code-protobuf-api v1.19.1-0.20250603030803-cbe2bfca5052/go.mod h1:ee6TzKbgMS42ZJgaFEMG3c4R3dGOiffHSu6MrY7WQvs=
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba h1:Bkp+gmeb6Y2PWXfkSCTMBGWkb2P1BujRDSjWeI+0j5I=
github.com/code-payments/code-vm-indexer v0.1.11-0.20241028132209-23031e814fba/go.mod h1:jSiifpiBpyBQ8q2R0MGEbkSgWC6sbdRTyDBntmW+j1E=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
Expand Down
60 changes: 48 additions & 12 deletions pkg/code/async/sequencer/fulfillment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/mr-tron/base58"

commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
indexerpb "github.com/code-payments/code-vm-indexer/generated/indexer/v1"

"github.com/code-payments/code-server/pkg/code/common"
Expand Down Expand Up @@ -209,20 +210,35 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) CanSubmitToBlockchain

// The source user account is a Code account, so we must validate it exists on
// the blockchain prior to sending funds from it.
isSourceAccountCreated, err := isTokenAccountOnBlockchain(ctx, h.data, fulfillmentRecord.Source)
isSourceAccountCreated, err := isAccountInitialized(ctx, h.data, fulfillmentRecord.Source)
if err != nil {
return false, err
} else if !isSourceAccountCreated {
return false, nil
}

// The destination user account might be a Code account or external wallet, so we
// must validate it exists on the blockchain prior to send funds to it.
isDestinationAccountCreated, err := isTokenAccountOnBlockchain(ctx, h.data, *fulfillmentRecord.Destination)
// must validate it exists on the blockchain prior to sending funds to it, or if we'll
// be creating it at time of send.
destinationAccount, err := common.NewAccountFromPublicKeyString(*fulfillmentRecord.Destination)
if err != nil {
return false, err
} else if !isDestinationAccountCreated {
return false, nil
}
isInternalTransfer, err := isInternalVmTransfer(ctx, h.data, destinationAccount)
if err != nil {
return false, err
}
hasCreateOnSendFee, err := h.data.HasFeeAction(ctx, fulfillmentRecord.Intent, transactionpb.FeePaymentAction_CREATE_ON_SEND_WITHDRAWAL)
if err != nil {
return false, err
}
if isInternalTransfer || !hasCreateOnSendFee {
isDestinationAccountCreated, err := isAccountInitialized(ctx, h.data, *fulfillmentRecord.Destination)
if err != nil {
return false, err
} else if !isDestinationAccountCreated {
return false, nil
}
}

// Check whether there's an earlier fulfillment that should be scheduled first
Expand Down Expand Up @@ -269,17 +285,17 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) SupportsOnDemandTrans
}

func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransaction(ctx context.Context, fulfillmentRecord *fulfillment.Record, selectedNonce *transaction_util.Nonce) (*solana.Transaction, error) {
virtualSignatureBytes, err := base58.Decode(*fulfillmentRecord.VirtualSignature)
actionRecord, err := h.data.GetActionById(ctx, fulfillmentRecord.Intent, fulfillmentRecord.ActionId)
if err != nil {
return nil, err
}

virtualNonce, err := common.NewAccountFromPublicKeyString(*fulfillmentRecord.VirtualNonce)
virtualSignatureBytes, err := base58.Decode(*fulfillmentRecord.VirtualSignature)
if err != nil {
return nil, err
}

actionRecord, err := h.data.GetActionById(ctx, fulfillmentRecord.Intent, fulfillmentRecord.ActionId)
virtualNonce, err := common.NewAccountFromPublicKeyString(*fulfillmentRecord.VirtualNonce)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -354,6 +370,24 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
*actionRecord.Quantity,
)
} else {
isCreateOnSend, err := h.data.HasFeeAction(ctx, fulfillmentRecord.Intent, transactionpb.FeePaymentAction_CREATE_ON_SEND_WITHDRAWAL)
if err != nil {
return &solana.Transaction{}, err
}

var destinationOwnerAccount *common.Account
if isCreateOnSend {
intentRecord, err := h.data.GetIntent(ctx, fulfillmentRecord.Intent)
if err != nil {
return nil, err
}

destinationOwnerAccount, err = common.NewAccountFromPublicKeyString(intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount)
if err != nil {
return nil, err
}
}

txn, makeTxnErr = transaction_util.MakeExternalTransferWithAuthorityTransaction(
selectedNonce.Account,
selectedNonce.Blockhash,
Expand All @@ -368,7 +402,10 @@ func (h *NoPrivacyTransferWithAuthorityFulfillmentHandler) MakeOnDemandTransacti
sourceMemory,
sourceIndex,

isCreateOnSend,
destinationOwnerAccount,
destinationTokenAccount,

*actionRecord.Quantity,
)
}
Expand Down Expand Up @@ -397,7 +434,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.

// The source user account is a Code account, so we must validate it exists on
// the blockchain prior to sending funds from it.
isSourceAccountCreated, err := isTokenAccountOnBlockchain(ctx, h.data, fulfillmentRecord.Source)
isSourceAccountCreated, err := isAccountInitialized(ctx, h.data, fulfillmentRecord.Source)
if err != nil {
return false, err
} else if !isSourceAccountCreated {
Expand All @@ -406,7 +443,7 @@ func (h *NoPrivacyWithdrawFulfillmentHandler) CanSubmitToBlockchain(ctx context.

// The destination user account might be a Code account or external wallet, so we
// must validate it exists on the blockchain prior to send funds to it.
isDestinationAccountCreated, err := isTokenAccountOnBlockchain(ctx, h.data, *fulfillmentRecord.Destination)
isDestinationAccountCreated, err := isAccountInitialized(ctx, h.data, *fulfillmentRecord.Destination)
if err != nil {
return false, err
} else if !isDestinationAccountCreated {
Expand Down Expand Up @@ -700,7 +737,6 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) OnFailure(ctx context.Cont
// is dust in the account.
//
// todo: Implement auto-recovery when we know the account is empty
// todo: Do "something" to indicate the client needs to resign a new transaction
return false, nil
}

Expand All @@ -712,7 +748,7 @@ func (h *CloseEmptyTimelockAccountFulfillmentHandler) IsRevoked(ctx context.Cont
return false, false, nil
}

func isTokenAccountOnBlockchain(ctx context.Context, data code_data.Provider, address string) (bool, error) {
func isAccountInitialized(ctx context.Context, data code_data.Provider, address string) (bool, error) {
// Try our cache of Code timelock accounts
timelockRecord, err := data.GetTimelockByVault(ctx, address)
if err == timelock.ErrTimelockNotFound {
Expand Down
8 changes: 2 additions & 6 deletions pkg/code/async/sequencer/intent_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ func (h *OpenAccountsIntentHandler) OnActionUpdated(ctx context.Context, intentI
}

for _, actionRecord := range actionRecords {
if actionRecord.ActionType != action.OpenAccount {
continue
}

// Intent is failed if at least one OpenAccount action fails
if actionRecord.State == action.StateFailed {
return markIntentFailed(ctx, h.data, intentId)
Expand Down Expand Up @@ -73,11 +69,11 @@ func (h *SendPublicPaymentIntentHandler) OnActionUpdated(ctx context.Context, in
}

actionRecordsToCheck := actionRecords
if len(actionRecords) > 1 {
if len(actionRecords) > 2 {
// Do not include the auto-return action, which is a different server-side
// initiated intent using the final action here.
//
// todo: Assumes > 1 case is just remote send
// todo: Assumes > 2 case is just remote send, but saves a DB call
actionRecordsToCheck = actionRecordsToCheck[:len(actionRecordsToCheck)-1]
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/code/common/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ func ValidateExternalTokenAccount(ctx context.Context, data code_data.Provider,
default:
// Unfortunate if Solana is down, but this only impacts withdraw flows,
// and we need to guarantee this isn't going to something that's not
// a Kin token acocunt.
// a core mint token acocunt.
return false, "", err
}
}
15 changes: 6 additions & 9 deletions pkg/code/common/subsidizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,15 @@ const (
)

// todo: doesn't consider external deposits
// todo: need a better system given fees are dynamic, we'll consider the worst case for each fulfillment type to be safe
var (
// This doesn't account for recovery of rent, which implies some fulfillments
// actually have negative fees. We often need to think about "in flight" costs
// and SOL balances for our subsidizer, so we exclude rent recovery which
// ensures our estimates are always on the conservative side of things.
lamportsByFulfillment = map[fulfillment.Type]uint64{
fulfillment.InitializeLockedTimelockAccount: 5000, // 0.000005 SOL (5000 lamports per signature)
fulfillment.NoPrivacyTransferWithAuthority: 5000, // 0.000005 SOL (5000 lamports per signature)
fulfillment.NoPrivacyWithdraw: 5000, // 0.000005 SOL (5000 lamports per signature)
fulfillment.CloseEmptyTimelockAccount: 5000, // 0.000005 SOL (5000 lamports per signature)
fulfillment.InitializeLockedTimelockAccount: 5050,
fulfillment.NoPrivacyTransferWithAuthority: 203928 + 5125,
fulfillment.NoPrivacyWithdraw: 5100,
fulfillment.CloseEmptyTimelockAccount: 5100,
}
lamportsPerCreateNonceAccount uint64 = 1450000 // 0.00145 SOL
lamportsPerCreateNonceAccount uint64 = 1450000
)

var (
Expand Down
18 changes: 15 additions & 3 deletions pkg/code/data/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"time"

transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"

"github.com/code-payments/code-server/pkg/code/data/intent"
"github.com/code-payments/code-server/pkg/pointer"
)
Expand Down Expand Up @@ -49,7 +51,7 @@ type Record struct {
Source string // Source token account involved
Destination *string // Destination token account involved, when it makes sense

// Kin quark amount involved, when it makes sense. This must be set for actions
// Core mint quark amount involved, when it makes sense. This must be set for actions
// that make balance changes across Code accounts! For deferred actions that are
// initially in the unknown state, the balance may be nil and updated at a later
// time. Store implementations will enforce which actions will allow quantity updates.
Expand All @@ -60,6 +62,8 @@ type Record struct {
// use cases before forming a firm opinion.
Quantity *uint64

FeeType *transactionpb.FeePaymentAction_FeeType

State State

CreatedAt time.Time
Expand Down Expand Up @@ -92,6 +96,10 @@ func (r *Record) Validate() error {
return errors.New("quantity is required when set")
}

if r.FeeType != nil && *r.FeeType == transactionpb.FeePaymentAction_UNKNOWN {
return errors.New("fee type is required when set")
}

return nil
}

Expand All @@ -109,6 +117,8 @@ func (r *Record) Clone() Record {
Destination: pointer.StringCopy(r.Destination),
Quantity: pointer.Uint64Copy(r.Quantity),

FeeType: (*transactionpb.FeePaymentAction_FeeType)(pointer.Int32Copy((*int32)(r.FeeType))),

State: r.State,

CreatedAt: r.CreatedAt,
Expand All @@ -125,8 +135,10 @@ func (r *Record) CopyTo(dst *Record) {
dst.ActionType = r.ActionType

dst.Source = r.Source
dst.Destination = r.Destination
dst.Quantity = r.Quantity
dst.Destination = pointer.StringCopy(r.Destination)
dst.Quantity = pointer.Uint64Copy(r.Quantity)

dst.FeeType = (*transactionpb.FeePaymentAction_FeeType)(pointer.Int32Copy((*int32)(r.FeeType)))

dst.State = r.State

Expand Down
29 changes: 29 additions & 0 deletions pkg/code/data/action/memory/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"sync"
"time"

transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"

"github.com/code-payments/code-server/pkg/code/data/action"
"github.com/code-payments/code-server/pkg/code/data/intent"
"github.com/code-payments/code-server/pkg/pointer"
Expand Down Expand Up @@ -111,6 +113,22 @@ func (s *store) filterByActionType(items []*action.Record, want action.Type) []*
return res
}

func (s *store) filterByFeeType(items []*action.Record, want transactionpb.FeePaymentAction_FeeType) []*action.Record {
var res []*action.Record

for _, item := range items {
if item.FeeType == nil {
continue
}

if *item.FeeType == want {
res = append(res, item)
}
}

return res
}

func (s *store) filterByState(items []*action.Record, include bool, states ...action.State) []*action.Record {
var res []*action.Record

Expand Down Expand Up @@ -295,6 +313,17 @@ func (s *store) GetGiftCardAutoReturnAction(ctx context.Context, giftCardVault s
return &cloned, nil
}

// CountFeeActions implements action.store.CountFeeActions
func (s *store) CountFeeActions(ctx context.Context, intent string, feeType transactionpb.FeePaymentAction_FeeType) (uint64, error) {
s.mu.Lock()
defer s.mu.Unlock()

items := s.findByIntent(intent)
items = s.filterByFeeType(items, feeType)
items = s.filterByState(items, false, action.StateRevoked)
return uint64(len(items)), nil
}

func (s *store) getNetBalance(account string) int64 {
var res int64

Expand Down
Loading
Loading