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 @@ -6,7 +6,7 @@ require (
filippo.io/edwards25519 v1.1.0
github.com/aws/aws-sdk-go-v2 v0.17.0
github.com/code-payments/code-vm-indexer v1.2.0
github.com/code-payments/ocp-protobuf-api v0.5.0
github.com/code-payments/ocp-protobuf-api v0.6.0
github.com/emirpasic/gods v1.12.0
github.com/envoyproxy/protoc-gen-validate v1.2.1
github.com/golang/protobuf v1.5.4
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/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-vm-indexer v1.2.0 h1:rSHpBMiT9BKgmKcXg/VIoi/h0t7jNxGx07Qz59m+6Q0=
github.com/code-payments/code-vm-indexer v1.2.0/go.mod h1:vn91YN2qNqb+gGJeZe2+l+TNxVmEEiRHXXnIn2Y40h8=
github.com/code-payments/ocp-protobuf-api v0.5.0 h1:ns3pztvgukRubQlJcuRxAiQmpmCqZimtkCA1pTFJpF8=
github.com/code-payments/ocp-protobuf-api v0.5.0/go.mod h1:tw6BooY5a8l6CtSZnKOruyKII0W04n89pcM4BizrgG8=
github.com/code-payments/ocp-protobuf-api v0.6.0 h1:dv/QWox20Z8RRHEwPSlWMAJPDMIvkzw97FESZArS8WA=
github.com/code-payments/ocp-protobuf-api v0.6.0/go.mod h1:tw6BooY5a8l6CtSZnKOruyKII0W04n89pcM4BizrgG8=
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
5 changes: 3 additions & 2 deletions ocp/antispam/guard.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/code-payments/ocp-server/metrics"
"github.com/code-payments/ocp-server/ocp/common"
"github.com/code-payments/ocp-server/ocp/data/swap"
)

type Guard struct {
Expand Down Expand Up @@ -87,11 +88,11 @@ func (g *Guard) AllowDistribution(ctx context.Context, owner *common.Account, is
return allow, nil
}

func (g *Guard) AllowSwap(ctx context.Context, owner, fromMint, toMint *common.Account) (bool, error) {
func (g *Guard) AllowSwap(ctx context.Context, fundingSource swap.FundingSource, owner, fromMint, toMint *common.Account) (bool, error) {
tracer := metrics.TraceMethodCall(ctx, metricsStructName, "AllowSwap")
defer tracer.End()

allow, reason, err := g.integration.AllowSwap(ctx, owner, fromMint, toMint)
allow, reason, err := g.integration.AllowSwap(ctx, fundingSource, owner, fromMint, toMint)
if err != nil {
return false, err
}
Expand Down
5 changes: 3 additions & 2 deletions ocp/antispam/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
transactionpb "github.com/code-payments/ocp-protobuf-api/generated/go/transaction/v1"

"github.com/code-payments/ocp-server/ocp/common"
"github.com/code-payments/ocp-server/ocp/data/swap"
)

// Integration is an antispam guard integration that apps can implement to check
Expand All @@ -21,7 +22,7 @@ type Integration interface {

AllowDistribution(ctx context.Context, owner *common.Account, isPublic bool) (bool, string, error)

AllowSwap(ctx context.Context, owner, fromMint, toMint *common.Account) (bool, string, error)
AllowSwap(ctx context.Context, fundingSource swap.FundingSource, owner, fromMint, toMint *common.Account) (bool, string, error)
}

type allowEverythingIntegration struct {
Expand Down Expand Up @@ -52,6 +53,6 @@ func (i *allowEverythingIntegration) AllowDistribution(ctx context.Context, owne
return true, "", nil
}

func (i *allowEverythingIntegration) AllowSwap(ctx context.Context, owner, fromMint, toMint *common.Account) (bool, string, error) {
func (i *allowEverythingIntegration) AllowSwap(ctx context.Context, fundingSource swap.FundingSource, owner, fromMint, toMint *common.Account) (bool, string, error) {
return true, "", nil
}
1 change: 1 addition & 0 deletions ocp/data/swap/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type FundingSource uint8
const (
FundingSourceUnknown = iota
FundingSourceSubmitIntent
FundingSourceExternalWallet
)

type Record struct {
Expand Down
89 changes: 70 additions & 19 deletions ocp/rpc/transaction/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,19 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
log.With(zap.Error(err)).Warn("invalid source mint account")
return handleStartSwapError(streamer, err)
}
log = log.With(zap.String("from_mint", fromMint.PublicKey().ToBase58()))

toMint, err := common.NewAccountFromProto(startCurrencyCreatorSwapReq.ToMint)
if err != nil {
log.With(zap.Error(err)).Warn("invalid destination mint account")
return handleStartSwapError(streamer, err)
}
log = log.With(zap.String("to_mint", toMint.PublicKey().ToBase58()))

log = log.With(
zap.String("funding_source", startCurrencyCreatorSwapReq.FundingSource.String()),
zap.String("funding_id", startCurrencyCreatorSwapReq.FundingId),
)

//
// Section: Antispam
Expand All @@ -96,7 +103,7 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
return handleStartSwapError(streamer, NewSwapDeniedError("not an ocp account"))
}

allow, err := s.antispamGuard.AllowSwap(ctx, owner, fromMint, toMint)
allow, err := s.antispamGuard.AllowSwap(ctx, swap.FundingSource(startCurrencyCreatorSwapReq.FundingSource), owner, fromMint, toMint)
if err != nil {
return handleStartSwapError(streamer, err)
} else if !allow {
Expand All @@ -111,7 +118,15 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
if err == nil {
return handleStartSwapError(streamer, NewSwapDeniedError("attempt to reuse swap id"))
} else if err != swap.ErrNotFound {
log.With(zap.Error(err)).Warn("failure checking for existing swap record")
log.With(zap.Error(err)).Warn("failure checking for existing swap record by id")
return handleStartSwapError(streamer, err)
}

_, err = s.data.GetSwapByFundingId(ctx, startCurrencyCreatorSwapReq.FundingId)
if err == nil {
return handleStartSwapError(streamer, NewSwapDeniedError("attempt to reuse swap funding id"))
} else if err != swap.ErrNotFound {
log.With(zap.Error(err)).Warn("failure checking for existing swap record by funding id")
return handleStartSwapError(streamer, err)
}

Expand Down Expand Up @@ -141,7 +156,7 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw

ownerSourceTimelockVault, err := owner.ToTimelockVault(sourceVmConfig)
if err != nil {
log.With(zap.Error(err)).Warn("failure getting owner destination timelock vault")
log.With(zap.Error(err)).Warn("failure getting owner source timelock vault")
return handleStartSwapError(streamer, err)
}

Expand All @@ -153,15 +168,6 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
return handleStartSwapError(streamer, err)
}

balance, err := balance.CalculateFromCache(ctx, s.data, ownerSourceTimelockVault)
if err != nil {
log.With(zap.Error(err)).Warn("failure getting owner source timelock vault balance")
return handleStartSwapError(streamer, err)
}
if balance < startCurrencyCreatorSwapReq.Amount {
return handleStartSwapError(streamer, NewSwapValidationError("insufficient balance"))
}

ownerDestinationTimelockVault, err := owner.ToTimelockVault(destinationVmConfig)
if err != nil {
log.With(zap.Error(err)).Warn("failure getting owner destination timelock vault")
Expand All @@ -176,12 +182,47 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
return handleStartSwapError(streamer, err)
}

_, err = s.data.GetIntent(ctx, startCurrencyCreatorSwapReq.FundingId)
if err == nil {
return handleStartSwapError(streamer, NewSwapValidationError("funding intent already exists"))
} else if err != intent.ErrIntentNotFound {
log.With(zap.Error(err)).Warn("failure getting funding intent record")
return handleStartSwapError(streamer, err)
switch startCurrencyCreatorSwapReq.FundingSource {
case transactionpb.FundingSource_FUNDING_SOURCE_SUBMIT_INTENT:
decodedFundingId, err := base58.Decode(startCurrencyCreatorSwapReq.FundingId)
if err != nil || len(decodedFundingId) != ed25519.PublicKeySize {
log.With(zap.Error(err)).Warn("invalid funding id")
return handleStartSwapError(streamer, NewSwapValidationError("funding id is not a public key"))
}

_, err = s.data.GetIntent(ctx, startCurrencyCreatorSwapReq.FundingId)
if err == nil {
return handleStartSwapError(streamer, NewSwapValidationError("funding intent already exists"))
} else if err != intent.ErrIntentNotFound {
log.With(zap.Error(err)).Warn("failure getting funding intent record")
return handleStartSwapError(streamer, err)
}

balance, err := balance.CalculateFromCache(ctx, s.data, ownerSourceTimelockVault)
if err != nil {
log.With(zap.Error(err)).Warn("failure getting owner source timelock vault balance")
return handleStartSwapError(streamer, err)
}
if balance < startCurrencyCreatorSwapReq.Amount {
return handleStartSwapError(streamer, NewSwapValidationError("insufficient balance"))
}
case transactionpb.FundingSource_FUNDING_SOURCE_EXTERNAL_WALLET:
decodedFundingId, err := base58.Decode(startCurrencyCreatorSwapReq.FundingId)
if err != nil || len(decodedFundingId) != ed25519.SignatureSize {
log.With(zap.Error(err)).Warn("invalid funding id")
return handleStartSwapError(streamer, NewSwapValidationError("funding id is not a signature"))
}

_, err = s.data.GetTransaction(ctx, startCurrencyCreatorSwapReq.FundingId)
if err == nil {
// Current flows expect that client will call StartSwap before submitting the transaction
return handleStartSwapError(streamer, NewSwapValidationError("funding transaction already exists"))
} else if err != solana.ErrSignatureNotFound {
log.With(zap.Error(err)).Warn("failure getting funding transaction")
return handleStartSwapError(streamer, err)
}
default:
return handleStartSwapError(streamer, NewSwapDeniedErrorf("funding source %s is not supported", startCurrencyCreatorSwapReq.FundingSource))
}

//
Expand Down Expand Up @@ -256,6 +297,16 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
// Section: Swap state DB commit
//

var initialState swap.State
switch startCurrencyCreatorSwapReq.FundingSource {
case transactionpb.FundingSource_FUNDING_SOURCE_SUBMIT_INTENT:
initialState = swap.StateCreated
case transactionpb.FundingSource_FUNDING_SOURCE_EXTERNAL_WALLET:
initialState = swap.StateFunding
default:
return handleStartSwapError(streamer, NewSwapDeniedErrorf("funding source %s is not supported", startCurrencyCreatorSwapReq.FundingSource))
}

record := &swap.Record{
SwapId: swapId,
Owner: owner.PublicKey().ToBase58(),
Expand All @@ -269,7 +320,7 @@ func (s *transactionServer) StartSwap(streamer transactionpb.Transaction_StartSw
ProofSignature: base58.Encode(submitSignatureReq.Signature.Value),
TransactionSignature: nil,
TransactionBlob: nil,
State: swap.StateCreated,
State: initialState,
CreatedAt: time.Now(),
}

Expand Down
17 changes: 11 additions & 6 deletions ocp/worker/swap/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ const (

ClientTimeoutToSwapConfigEnvName = envConfigPrefix + "CLIENT_TIMEOUT_TO_SWAP"
defaultClientTimeoutToSwap = 5 * time.Minute

ExternalWalletFinalizationTimeoutConfigEnvName = envConfigPrefix + "EXTERNAL_WALLET_FINALIZATION_TIMEOUT"
defaultExternalWalletFinalizationTimeout = 90 * time.Second
)

type conf struct {
batchSize config.Uint64
clientTimeoutToFund config.Duration
clientTimeoutToSwap config.Duration
batchSize config.Uint64
clientTimeoutToFund config.Duration
clientTimeoutToSwap config.Duration
externalWalletFinalizationTimeout config.Duration
}

// ConfigProvider defines how config values are pulled
Expand All @@ -33,9 +37,10 @@ type ConfigProvider func() *conf
func WithEnvConfigs() ConfigProvider {
return func() *conf {
return &conf{
batchSize: env.NewUint64Config(BatchSizeConfigEnvName, defaultFulfillmentBatchSize),
clientTimeoutToFund: env.NewDurationConfig(ClientTimeoutToFundConfigEnvName, defaultClientTimeoutToFund),
clientTimeoutToSwap: env.NewDurationConfig(ClientTimeoutToSwapConfigEnvName, defaultClientTimeoutToSwap),
batchSize: env.NewUint64Config(BatchSizeConfigEnvName, defaultFulfillmentBatchSize),
clientTimeoutToFund: env.NewDurationConfig(ClientTimeoutToFundConfigEnvName, defaultClientTimeoutToFund),
clientTimeoutToSwap: env.NewDurationConfig(ClientTimeoutToSwapConfigEnvName, defaultClientTimeoutToSwap),
externalWalletFinalizationTimeout: env.NewDurationConfig(ExternalWalletFinalizationTimeoutConfigEnvName, defaultExternalWalletFinalizationTimeout),
}
}
}
46 changes: 44 additions & 2 deletions ocp/worker/swap/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ func (p *runtime) markSwapCancelling(ctx context.Context, record *swap.Record, t

func (p *runtime) markSwapCancelled(ctx context.Context, record *swap.Record) error {
return p.data.ExecuteInTx(ctx, sql.LevelDefault, func(ctx context.Context) error {
err := p.validateSwapState(record, swap.StateCreated, swap.StateCancelling)
err := p.validateSwapState(record, swap.StateCreated, swap.StateFunding, swap.StateCancelling)
if err != nil {
return err
}

switch record.State {
case swap.StateCreated:
case swap.StateCreated, swap.StateFunding:
err = p.markNonceAvailableDueToCancelledSwap(ctx, record)
if err != nil {
return err
Expand Down Expand Up @@ -482,6 +482,48 @@ func (p *runtime) markNonceAvailableDueToCancelledSwap(ctx context.Context, reco
return p.data.SaveNonce(ctx, nonceRecord)
}

func (p *runtime) validateExternalWalletFundingTransaction(ctx context.Context, record *swap.Record) (bool, error) {
if record.FundingSource != swap.FundingSourceExternalWallet {
return false, errors.New("invalid funding source`")
}

owner, err := common.NewAccountFromPublicKeyString(record.Owner)
if err != nil {
return false, errors.Wrap(err, "error parsing owner")
}

fromMint, err := common.NewAccountFromPublicKeyString(record.FromMint)
if err != nil {
return false, errors.Wrap(err, "error parsing from mint")
}

sourceVmConfig, err := common.GetVmConfigForMint(ctx, p.data, fromMint)
if err != nil {
return false, errors.Wrap(err, "error getting vm config for source mint")
}

swapAta, err := owner.ToVmSwapAta(sourceVmConfig)
if err != nil {
return false, errors.Wrap(err, "error getting swap ata")
}

tokenBalances, err := p.data.GetBlockchainTransactionTokenBalances(ctx, record.FundingId)
if err != nil {
return false, errors.Wrap(err, "error getting token balances")
}

deltaQuarks, err := transaction_util.GetDeltaQuarksFromTokenBalances(swapAta, tokenBalances)
if err != nil {
return false, errors.Wrap(err, "error getting delta quarks from token balances")
}

if deltaQuarks != int64(record.Amount) {
return false, nil
}

return true, nil
}

func getSwapDepositIntentID(signature string, destination *common.Account) string {
combined := fmt.Sprintf("%s-%s", signature, destination.PublicKey().ToBase58())
hashed := sha256.Sum256([]byte(combined))
Expand Down
Loading
Loading