Skip to content

Swap RPC implementation #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 22, 2024
Merged
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ require (
firebase.google.com/go/v4 v4.8.0
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.8.2
github.com/code-payments/code-protobuf-api v1.8.3
github.com/emirpasic/gods v1.12.0
github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.3.0
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
Expand Down Expand Up @@ -97,7 +96,6 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
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.8.2 h1:fl5BS54jI8kqFG9qOyvkwdmZNX9FyG0WvgUTpu/2/dg=
github.com/code-payments/code-protobuf-api v1.8.2/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
github.com/code-payments/code-protobuf-api v1.8.3 h1:BEGKUvHZu5TvfsX4zhzSppuvZGlHH/DbvVBS0/oB18M=
github.com/code-payments/code-protobuf-api v1.8.3/go.mod h1:pHQm75vydD6Cm2qHAzlimW6drysm489Z4tVxC2zHSsU=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down Expand Up @@ -175,8 +175,6 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -573,8 +571,6 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
2 changes: 2 additions & 0 deletions pkg/code/antispam/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const (

actionWelcomeBonus = "WelcomeBonus"
actionReferralBonus = "ReferralBonus"

actionSwap = "Swap"
)

func recordDenialEvent(ctx context.Context, action, reason string) {
Expand Down
79 changes: 79 additions & 0 deletions pkg/code/antispam/swap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package antispam

import (
"context"

"github.com/sirupsen/logrus"

"github.com/code-payments/code-server/pkg/code/common"
"github.com/code-payments/code-server/pkg/code/data/phone"
"github.com/code-payments/code-server/pkg/code/data/user/identity"
"github.com/code-payments/code-server/pkg/grpc/client"
"github.com/code-payments/code-server/pkg/metrics"
)

// AllowSwap determines whether a phone-verified owner account can perform a swap.
// The objective here is to limit attacks against our Swap Subsidizer's SOL balance.
//
// todo: needs tests
func (g *Guard) AllowSwap(ctx context.Context, owner *common.Account) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowSwap")
defer tracer.End()

log := g.log.WithFields(logrus.Fields{
"method": "AllowSwap",
"owner": owner.PublicKey().ToBase58(),
})
log = client.InjectLoggingMetadata(ctx, log)

// Deny abusers from known IPs
if isIpBanned(ctx) {
log.Info("ip is banned")
recordDenialEvent(ctx, actionSwap, "ip banned")
return false, nil
}

verification, err := g.data.GetLatestPhoneVerificationForAccount(ctx, owner.PublicKey().ToBase58())
if err == phone.ErrVerificationNotFound {
// Owner account was never phone verified, so deny the action.
log.Info("owner account is not phone verified")
recordDenialEvent(ctx, actionSwap, "not phone verified")
return false, nil
} else if err != nil {
tracer.OnError(err)
log.WithError(err).Warn("failure getting phone verification record")
return false, err
}

log = log.WithField("phone", verification.PhoneNumber)

// Deny abusers from known phone ranges
if hasBannedPhoneNumberPrefix(verification.PhoneNumber) {
log.Info("denying phone prefix")
recordDenialEvent(ctx, actionSwap, "phone prefix banned")
return false, nil
}

user, err := g.data.GetUserByPhoneView(ctx, verification.PhoneNumber)
switch err {
case nil:
// Deny banned users forever
if user.IsBanned {
log.Info("denying banned user")
recordDenialEvent(ctx, actionSwap, "user banned")
return false, nil
}

// Staff users have unlimited access to enable testing and demoing.
if user.IsStaffUser {
return true, nil
}
case identity.ErrNotFound:
default:
tracer.OnError(err)
log.WithError(err).Warn("failure getting user identity by phone view")
return false, err
}

return true, nil
}
24 changes: 18 additions & 6 deletions pkg/code/server/grpc/transaction/v2/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ const (
SubmitIntentTimeoutConfigEnvName = envConfigPrefix + "SUBMIT_INTENT_TIMEOUT"
defaultSubmitIntentTimeout = 5 * time.Second

SubmitIntentReceiveTimeoutConfigEnvName = envConfigPrefix + "SUBMIT_INTENT_RECEIVE_TIMEOUT"
defaultSubmitIntentReceiveTimeout = time.Second
SwapTimeoutConfigEnvName = envConfigPrefix + "SWAP_TIMEOUT"
defaultSwapTimeout = 60 * time.Second

ClientReceiveTimeoutConfigEnvName = envConfigPrefix + "CLIENT_RECEIVE_TIMEOUT"
defaultClientReceiveTimeout = time.Second

FeeCollectorTokenPublicKeyConfigEnvName = envConfigPrefix + "FEE_COLLECTOR_TOKEN_PUBLIC_KEY"
defaultFeeCollectorPublicKey = "invalid" // Ensure something valid is set
Expand All @@ -33,6 +36,9 @@ const (
AirdropperOwnerPublicKeyEnvName = envConfigPrefix + "AIRDROPPER_OWNER_PUBLIC_KEY"
defaultAirdropperOwnerPublicKey = "invalid" // Ensure something valid is set

SwapSubsidizerOwnerPublicKeyEnvName = envConfigPrefix + "SWAP_SUBSIDIZER_OWNER_PUBLIC_KEY"
defaultSwapSubsidizerOwnerPublicKey = "invalid" // Ensure something valid is set

TreasuryPoolOneKinBucketConfigEnvName = envConfigPrefix + "TREASURY_POOL_1_KIN_BUCKET"
TreasuryPoolTenKinBucketConfigEnvName = envConfigPrefix + "TREASURY_POOL_10_KIN_BUCKET"
TreasuryPoolHundredKinBucketConfigEnvName = envConfigPrefix + "TREASURY_POOL_100_KIN_BUCKET"
Expand All @@ -55,11 +61,13 @@ type conf struct {
disableAmlChecks config.Bool // To avoid limits during testing
disableBlockchainChecks config.Bool
submitIntentTimeout config.Duration
submitIntentReceiveTimeout config.Duration
swapTimeout config.Duration
clientReceiveTimeout config.Duration
feeCollectorTokenPublicKey config.String
enableAirdrops config.Bool
enableAsyncAirdropProcessing config.Bool
airdropperOwnerPublicKey config.String
swapSubsidizerOwnerPublicKey config.String
treasuryPoolOneKinBucket config.String
treasuryPoolTenKinBucket config.String
treasuryPoolHundredKinBucket config.String
Expand All @@ -84,11 +92,13 @@ func WithEnvConfigs() ConfigProvider {
disableAmlChecks: wrapper.NewBoolConfig(memory.NewConfig(false), false),
disableBlockchainChecks: env.NewBoolConfig(DisableBlockchainChecksConfigEnvName, defaultDisableBlockchainChecks),
submitIntentTimeout: env.NewDurationConfig(SubmitIntentTimeoutConfigEnvName, defaultSubmitIntentTimeout),
submitIntentReceiveTimeout: env.NewDurationConfig(SubmitIntentReceiveTimeoutConfigEnvName, defaultSubmitIntentReceiveTimeout),
swapTimeout: env.NewDurationConfig(SwapTimeoutConfigEnvName, defaultSwapTimeout),
clientReceiveTimeout: env.NewDurationConfig(ClientReceiveTimeoutConfigEnvName, defaultClientReceiveTimeout),
feeCollectorTokenPublicKey: env.NewStringConfig(FeeCollectorTokenPublicKeyConfigEnvName, defaultFeeCollectorPublicKey),
enableAirdrops: env.NewBoolConfig(EnableAirdropsConfigEnvName, defaultEnableAirdrops),
enableAsyncAirdropProcessing: wrapper.NewBoolConfig(memory.NewConfig(true), true),
airdropperOwnerPublicKey: env.NewStringConfig(AirdropperOwnerPublicKeyEnvName, defaultAirdropperOwnerPublicKey),
swapSubsidizerOwnerPublicKey: env.NewStringConfig(SwapSubsidizerOwnerPublicKeyEnvName, defaultSwapSubsidizerOwnerPublicKey),
treasuryPoolOneKinBucket: env.NewStringConfig(TreasuryPoolOneKinBucketConfigEnvName, defaultTreasuryPoolName),
treasuryPoolTenKinBucket: env.NewStringConfig(TreasuryPoolTenKinBucketConfigEnvName, defaultTreasuryPoolName),
treasuryPoolHundredKinBucket: env.NewStringConfig(TreasuryPoolHundredKinBucketConfigEnvName, defaultTreasuryPoolName),
Expand All @@ -108,7 +118,7 @@ type testOverrides struct {
enableAntispamChecks bool
enableAmlChecks bool
enableAirdrops bool
submitIntentReceiveTimeout time.Duration
clientReceiveTimeout time.Duration
feeCollectorTokenPublicKey string
treasuryPoolOneKinBucket string
treasuryPoolTenKinBucket string
Expand All @@ -127,11 +137,13 @@ func withManualTestOverrides(overrides *testOverrides) ConfigProvider {
disableAmlChecks: wrapper.NewBoolConfig(memory.NewConfig(!overrides.enableAmlChecks), false),
disableBlockchainChecks: wrapper.NewBoolConfig(memory.NewConfig(true), true),
submitIntentTimeout: wrapper.NewDurationConfig(memory.NewConfig(defaultSubmitIntentTimeout), defaultSubmitIntentTimeout),
submitIntentReceiveTimeout: wrapper.NewDurationConfig(memory.NewConfig(overrides.submitIntentReceiveTimeout), defaultSubmitIntentReceiveTimeout),
swapTimeout: wrapper.NewDurationConfig(memory.NewConfig(defaultSwapTimeout), defaultSwapTimeout),
clientReceiveTimeout: wrapper.NewDurationConfig(memory.NewConfig(overrides.clientReceiveTimeout), defaultClientReceiveTimeout),
feeCollectorTokenPublicKey: wrapper.NewStringConfig(memory.NewConfig(overrides.feeCollectorTokenPublicKey), defaultFeeCollectorPublicKey),
enableAirdrops: wrapper.NewBoolConfig(memory.NewConfig(overrides.enableAirdrops), false),
enableAsyncAirdropProcessing: wrapper.NewBoolConfig(memory.NewConfig(false), false),
airdropperOwnerPublicKey: wrapper.NewStringConfig(memory.NewConfig(defaultAirdropperOwnerPublicKey), defaultAirdropperOwnerPublicKey),
swapSubsidizerOwnerPublicKey: wrapper.NewStringConfig(memory.NewConfig(defaultSwapSubsidizerOwnerPublicKey), defaultSwapSubsidizerOwnerPublicKey),
treasuryPoolOneKinBucket: wrapper.NewStringConfig(memory.NewConfig(overrides.treasuryPoolOneKinBucket), defaultTreasuryPoolName),
treasuryPoolTenKinBucket: wrapper.NewStringConfig(memory.NewConfig(overrides.treasuryPoolTenKinBucket), defaultTreasuryPoolName),
treasuryPoolHundredKinBucket: wrapper.NewStringConfig(memory.NewConfig(overrides.treasuryPoolHundredKinBucket), defaultTreasuryPoolName),
Expand Down
91 changes: 90 additions & 1 deletion pkg/code/server/grpc/transaction/v2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"

"github.com/code-payments/code-server/pkg/solana"
"github.com/code-payments/code-server/pkg/code/transaction"
"github.com/code-payments/code-server/pkg/solana"
)

const (
Expand Down Expand Up @@ -84,6 +84,42 @@ func (e IntentDeniedError) Error() string {
return e.message
}

type SwapValidationError struct {
message string
}

func newSwapValidationError(message string) SwapValidationError {
return SwapValidationError{
message: message,
}
}

func newSwapValidationErrorf(format string, args ...any) SwapValidationError {
return newSwapValidationError(fmt.Sprintf(format, args...))
}

func (e SwapValidationError) Error() string {
return e.message
}

type SwapDeniedError struct {
message string
}

func newSwapDeniedError(message string) SwapDeniedError {
return SwapDeniedError{
message: message,
}
}

func newSwapDeniedErrorf(format string, args ...any) SwapDeniedError {
return newSwapDeniedError(fmt.Sprintf(format, args...))
}

func (e SwapDeniedError) Error() string {
return e.message
}

type StaleStateError struct {
message string
}
Expand Down Expand Up @@ -209,3 +245,56 @@ func handleSubmitIntentStructuredError(streamer transactionpb.Transaction_Submit
}
return streamer.Send(errResp)
}

func handleSwapError(streamer transactionpb.Transaction_SwapServer, err error) error {
// gRPC status errors are passed through as is
if _, ok := status.FromError(err); ok {
return err
}

// Case 1: Errors that map to a Code error response
switch err.(type) {
case SwapValidationError:
return handleSwapStructuredError(
streamer,
transactionpb.SwapResponse_Error_INVALID_SWAP,
toReasonStringErrorDetails(err),
)
case SwapDeniedError:
return handleSwapStructuredError(
streamer,
transactionpb.SwapResponse_Error_DENIED,
toReasonStringErrorDetails(err),
)
}

switch err {
case ErrInvalidSignature:
return handleSwapStructuredError(
streamer,
transactionpb.SwapResponse_Error_SIGNATURE_ERROR,
toReasonStringErrorDetails(err),
)
case ErrNotImplemented:
return status.Error(codes.Unimplemented, err.Error())
}

// Case 2: Errors that map to gRPC status errors
switch err {
case ErrTimedOutReceivingRequest:
return status.Error(codes.DeadlineExceeded, err.Error())
}
return status.Error(codes.Internal, "rpc server failure")
}

func handleSwapStructuredError(streamer transactionpb.Transaction_SwapServer, code transactionpb.SwapResponse_Error_Code, errorDetails ...*transactionpb.ErrorDetails) error {
errResp := &transactionpb.SwapResponse{
Response: &transactionpb.SwapResponse_Error_{
Error: &transactionpb.SwapResponse_Error{
Code: code,
ErrorDetails: errorDetails,
},
},
}
return streamer.Send(errResp)
}
8 changes: 4 additions & 4 deletions pkg/code/server/grpc/transaction/v2/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
messagingpb "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1"
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"

"github.com/code-payments/code-server/pkg/code/chat"
chat_util "github.com/code-payments/code-server/pkg/code/chat"
"github.com/code-payments/code-server/pkg/code/common"
code_data "github.com/code-payments/code-server/pkg/code/data"
Expand All @@ -46,7 +45,8 @@ import (
)

const (
// Assumes the client signature index is consistent across all transactions.
// Assumes the client signature index is consistent across all transactions,
// including those constructed in the SubmitIntent and Swap RPCs.
clientSignatureIndex = 1
)

Expand Down Expand Up @@ -800,7 +800,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
}
}

var chatMessagesToPush []*chat.MessageWithOwner
var chatMessagesToPush []*chat_util.MessageWithOwner

// Save all of the required DB records in one transaction to complete the
// intent operation. It's very bad if we end up failing halfway through.
Expand Down Expand Up @@ -1025,7 +1025,7 @@ func (s *transactionServer) boundedSubmitIntentRecv(ctx context.Context, streame
}()

select {
case <-time.After(s.conf.submitIntentReceiveTimeout.Get(ctx)):
case <-time.After(s.conf.clientReceiveTimeout.Get(ctx)):
return nil, ErrTimedOutReceivingRequest
case <-done:
return req, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/code/server/grpc/transaction/v2/intent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2696,7 +2696,7 @@ func TestSubmitIntent_InvalidNumberOfSignaturesSubmitted(t *testing.T) {

func TestSubmitIntent_TimeBoundedRequestSend(t *testing.T) {
server, phone, _, cleanup := setupTestEnv(t, &testOverrides{
submitIntentReceiveTimeout: 100 * time.Millisecond,
clientReceiveTimeout: 100 * time.Millisecond,
})
defer cleanup()

Expand Down
Loading