├── .gitignore
├── LICENSE.md
├── Makefile
├── README.md
├── SECURITY.md
├── go.mod
├── go.sum
└── pkg
├── cache
├── cache.go
└── cache_test.go
├── cluster
├── cluster.go
├── doc.go
├── etcd
│ ├── etcd.go
│ └── etcd_test.go
├── grpc
│ ├── balancer.go
│ ├── context.go
│ ├── example
│ │ ├── cluster.go
│ │ └── cluster_test.go
│ ├── picker.go
│ └── resolver.go
├── memory
│ ├── memory.go
│ └── memory_test.go
├── registry
│ ├── cluster.go
│ └── registry.go
├── ring
│ ├── ring.go
│ └── ring_test.go
└── tests
│ └── cluster_tests.go
├── code
├── aml
│ ├── guard.go
│ ├── guard_test.go
│ └── metrics.go
├── antispam
│ ├── guard.go
│ ├── integration.go
│ └── metrics.go
├── async
│ ├── account
│ │ ├── config.go
│ │ ├── gift_card.go
│ │ ├── gift_card_test.go
│ │ ├── metrics.go
│ │ ├── service.go
│ │ ├── swap.go
│ │ ├── swap_test.go
│ │ └── testutil.go
│ ├── airdrop
│ │ ├── config.go
│ │ ├── indexer.go
│ │ ├── integration.go
│ │ ├── nonce.go
│ │ ├── service.go
│ │ ├── transaction.go
│ │ └── worker.go
│ ├── currency
│ │ └── exchange_rate.go
│ ├── geyser
│ │ ├── api
│ │ │ ├── Makefile
│ │ │ ├── gen
│ │ │ │ ├── geyser.pb.go
│ │ │ │ ├── geyser_grpc.pb.go
│ │ │ │ └── solana-storage.pb.go
│ │ │ └── proto
│ │ │ │ ├── geyser.proto
│ │ │ │ └── solana-storage.proto
│ │ ├── backup.go
│ │ ├── config.go
│ │ ├── consumer.go
│ │ ├── external_deposit.go
│ │ ├── handler.go
│ │ ├── handler_test.go
│ │ ├── metrics.go
│ │ ├── retry.go
│ │ ├── service.go
│ │ ├── subscription.go
│ │ ├── timelock.go
│ │ └── util.go
│ ├── nonce
│ │ ├── allocator.go
│ │ ├── config.go
│ │ ├── keys.go
│ │ ├── metrics.go
│ │ ├── pool.go
│ │ ├── pool_test.go
│ │ ├── service.go
│ │ └── util.go
│ ├── sequencer
│ │ ├── action_handler.go
│ │ ├── action_handler_test.go
│ │ ├── config.go
│ │ ├── fulfillment_handler.go
│ │ ├── fulfillment_handler_test.go
│ │ ├── intent_handler.go
│ │ ├── intent_handler_test.go
│ │ ├── metrics.go
│ │ ├── scheduler.go
│ │ ├── scheduler_test.go
│ │ ├── service.go
│ │ ├── testutil.go
│ │ ├── timelock.go
│ │ ├── utils.go
│ │ ├── utils_test.go
│ │ ├── vm.go
│ │ ├── worker.go
│ │ └── worker_test.go
│ ├── service.go
│ ├── vm
│ │ ├── service.go
│ │ └── storage.go
│ └── webhook
│ │ ├── config.go
│ │ ├── metrics.go
│ │ ├── service.go
│ │ ├── worker.go
│ │ └── worker_test.go
├── auth
│ ├── encoding.go
│ ├── encoding_test.go
│ ├── signature.go
│ └── signature_test.go
├── balance
│ ├── calculator.go
│ └── calculator_test.go
├── common
│ ├── account.go
│ ├── account_test.go
│ ├── key.go
│ ├── key_test.go
│ ├── mint.go
│ ├── mint_test.go
│ ├── owner.go
│ ├── owner_test.go
│ ├── subsidizer.go
│ ├── subsidizer_test.go
│ ├── testutil.go
│ └── vm.go
├── config
│ └── config.go
├── data
│ ├── account
│ │ ├── acccount_info.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── action
│ │ ├── action.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── balance
│ │ ├── checkpoint.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── blockchain.go
│ ├── config.go
│ ├── currency
│ │ ├── memory
│ │ │ └── store.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ ├── tests
│ │ │ └── tests.go
│ │ └── util
│ │ │ └── util.go
│ ├── cvm
│ │ ├── ram
│ │ │ ├── account.go
│ │ │ ├── memory
│ │ │ │ ├── store.go
│ │ │ │ └── store_test.go
│ │ │ ├── postgres
│ │ │ │ ├── model.go
│ │ │ │ ├── store.go
│ │ │ │ └── store_test.go
│ │ │ ├── store.go
│ │ │ ├── tests
│ │ │ │ └── tests.go
│ │ │ ├── util.go
│ │ │ └── util_test.go
│ │ └── storage
│ │ │ ├── account.go
│ │ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ │ ├── store.go
│ │ │ └── tests
│ │ │ └── tests.go
│ ├── deposit
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── estimated.go
│ ├── estimated_test.go
│ ├── external.go
│ ├── external_test.go
│ ├── fulfillment
│ │ ├── fulfillment.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── intent
│ │ ├── intent.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── internal.go
│ ├── merkletree
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── merkletree.go
│ │ ├── model.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── messaging
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── model_test.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── nonce
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── nonce.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── onramp
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── purchase.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── paymentrequest
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── payment_request.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── provider.go
│ ├── rendezvous
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ ├── timelock
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ ├── tests
│ │ │ └── tests.go
│ │ └── timelock.go
│ ├── transaction
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── store.go
│ │ ├── tests
│ │ │ └── tests.go
│ │ └── transaction.go
│ ├── vault
│ │ ├── encryption.go
│ │ ├── key.go
│ │ ├── key_test.go
│ │ ├── memory
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── postgres
│ │ │ ├── model.go
│ │ │ ├── store.go
│ │ │ └── store_test.go
│ │ ├── secret.go
│ │ ├── store.go
│ │ └── tests
│ │ │ └── tests.go
│ └── webhook
│ │ ├── memory
│ │ ├── store.go
│ │ └── store_test.go
│ │ ├── postgres
│ │ ├── model.go
│ │ ├── store.go
│ │ └── store_test.go
│ │ ├── store.go
│ │ ├── tests
│ │ └── tests.go
│ │ └── webhook.go
├── exchangerate
│ ├── time.go
│ └── validation.go
├── limit
│ └── limits.go
├── server
│ ├── account
│ │ ├── server.go
│ │ └── server_test.go
│ ├── currency
│ │ └── currency.go
│ ├── messaging
│ │ ├── client.go
│ │ ├── config.go
│ │ ├── error.go
│ │ ├── internal.go
│ │ ├── message_handler.go
│ │ ├── server.go
│ │ ├── server_test.go
│ │ ├── stream.go
│ │ └── testutil.go
│ ├── micropayment
│ │ ├── server.go
│ │ └── server_test.go
│ └── transaction
│ │ ├── action_handler.go
│ │ ├── airdrop.go
│ │ ├── airdrop_test.go
│ │ ├── config.go
│ │ ├── errors.go
│ │ ├── intent.go
│ │ ├── intent_handler.go
│ │ ├── intent_test.go
│ │ ├── limits.go
│ │ ├── limits_test.go
│ │ ├── local_simulation.go
│ │ ├── local_simulation_test.go
│ │ ├── metrics.go
│ │ ├── onramp.go
│ │ ├── onramp_test.go
│ │ ├── server.go
│ │ ├── swap.go
│ │ └── testutil.go
├── thirdparty
│ ├── domain.go
│ ├── domain_test.go
│ ├── encryption_test.go
│ ├── message.go
│ └── message_test.go
├── transaction
│ ├── instruction.go
│ ├── nonce.go
│ ├── nonce_test.go
│ ├── transaction.go
│ └── transaction_test.go
└── webhook
│ ├── execution.go
│ ├── execution_test.go
│ ├── payload.go
│ └── testutil.go
├── config
├── config.go
├── env
│ ├── config.go
│ └── config_test.go
├── memory
│ ├── config.go
│ └── config_test.go
└── wrapper
│ ├── wrappers.go
│ └── wrappers_test.go
├── currency
├── client.go
├── coingecko
│ └── client.go
├── fixer
│ └── client.go
└── iso.go
├── database
├── postgres
│ ├── client.go
│ ├── db.go
│ ├── errors.go
│ └── test
│ │ └── util.go
└── query
│ ├── cursor.go
│ ├── filter.go
│ ├── interval.go
│ ├── ordering.go
│ ├── query.go
│ └── utils.go
├── etcd
├── persistent_lease.go
├── persistent_lease_test.go
├── wait.go
├── wait_test.go
├── watch.go
└── watch_test.go
├── etcdtest
├── container.go
└── container_test.go
├── grpc
├── app
│ ├── app.go
│ ├── config.go
│ ├── loader.go
│ └── option.go
├── client
│ ├── device.go
│ ├── device_test.go
│ ├── ip.go
│ ├── ip_test.go
│ ├── logging.go
│ ├── user_agent.go
│ ├── user_agent_test.go
│ ├── version.go
│ └── version_test.go
├── headers
│ ├── client_interceptor.go
│ ├── headers.go
│ ├── headers_context.go
│ └── server_interceptor.go
├── metrics
│ └── new_relic_server_interceptor.go
├── protobuf
│ └── validation
│ │ ├── client_interceptor.go
│ │ ├── server_interceptor.go
│ │ └── test
│ │ ├── Makefile
│ │ ├── test.pb.go
│ │ ├── test.pb.validate.go
│ │ ├── test.proto
│ │ └── validation_test.go
├── util.go
└── util_test.go
├── jupiter
└── client.go
├── lock
├── etcd
│ ├── lock.go
│ └── lock_test.go
└── lock.go
├── merkletree
├── tree.go
└── tree_test.go
├── metrics
├── constants.go
├── events.go
├── logging.go
├── metrics.go
└── tracing.go
├── netutil
├── domain.go
├── ip.go
├── port.go
└── url.go
├── osutil
└── memory.go
├── pointer
└── pointer.go
├── rate
├── limiter.go
└── limiter_test.go
├── retry
├── backoff
│ ├── backoff.go
│ └── backoff_test.go
├── retry.go
├── retry_test.go
├── strategy.go
└── strategy_test.go
├── solana
├── address.go
├── address_test.go
├── binary
│ └── utils.go
├── client.go
├── client_test.go
├── computebudget
│ └── program.go
├── config.go
├── cvm
│ ├── accounts_code_vm.go
│ ├── accounts_memory_account.go
│ ├── accounts_relay.go
│ ├── accounts_storage.go
│ ├── accounts_unlock_state.go
│ ├── address.go
│ ├── instructions_compress.go
│ ├── instructions_decompress.go
│ ├── instructions_exec.go
│ ├── instructions_init_memory.go
│ ├── instructions_init_nonce.go
│ ├── instructions_init_relay.go
│ ├── instructions_init_storage.go
│ ├── instructions_init_timelock.go
│ ├── instructions_init_unlock.go
│ ├── instructions_init_vm.go
│ ├── instructions_resize_memory.go
│ ├── instructions_snapshot.go
│ ├── instructions_timelock_deposit.go
│ ├── program.go
│ ├── transaction.go
│ ├── types_account_type.go
│ ├── types_code_instruction.go
│ ├── types_hash.go
│ ├── types_item_state.go
│ ├── types_memory_allocator.go
│ ├── types_memory_layout.go
│ ├── types_memory_version.go
│ ├── types_merkle_tree.go
│ ├── types_message.go
│ ├── types_opcode.go
│ ├── types_recent_roots.go
│ ├── types_signature.go
│ ├── types_slice_allocator.go
│ ├── types_timelock_state.go
│ ├── types_token_pool.go
│ ├── types_virtual_account_type.go
│ ├── utils.go
│ ├── virtual_accounts.go
│ ├── virtual_accounts_durable_nonce.go
│ ├── virtual_accounts_relay_account.go
│ ├── virtual_accounts_timelock_account.go
│ ├── virtual_instructions.go
│ ├── virtual_instructions_airdrop.go
│ ├── virtual_instructions_conditional_transfer.go
│ ├── virtual_instructions_external_relay.go
│ ├── virtual_instructions_external_transfer.go
│ ├── virtual_instructions_external_withdraw.go
│ ├── virtual_instructions_relay.go
│ ├── virtual_instructions_transfer.go
│ └── virtual_instructions_withdraw.go
├── ed25519
│ └── program.go
├── encoding.go
├── errors.go
├── errors_test.go
├── instruction.go
├── memo
│ ├── program.go
│ └── program_test.go
├── shortvec
│ ├── shortvec.go
│ └── shortvec_test.go
├── splitter
│ ├── accounts_pool.go
│ ├── accounts_proof.go
│ ├── address.go
│ ├── address_test.go
│ ├── errors.go
│ ├── instructions_close_proof.go
│ ├── instructions_close_token_account.go
│ ├── instructions_initialize_pool.go
│ ├── instructions_initialize_proof.go
│ ├── instructions_open_token_account.go
│ ├── instructions_save_recent_root.go
│ ├── instructions_transfer_with_commitment.go
│ ├── instructions_update_proof.go
│ ├── instructions_verify_proof.go
│ ├── legacy.go
│ ├── legacy_test.go
│ ├── program.go
│ ├── types.go
│ ├── types_hash.go
│ ├── types_merkletree.go
│ └── utils.go
├── swapvalidator
│ ├── address.go
│ ├── address_test.go
│ ├── errors.go
│ ├── instructions_post_swap.go
│ ├── instructions_pre_swap.go
│ ├── legacy.go
│ ├── program.go
│ └── utils.go
├── system
│ ├── accounts.go
│ ├── program.go
│ ├── program_test.go
│ └── sysvar.go
├── timelock
│ ├── legacy_2022
│ │ ├── accounts.go
│ │ ├── address.go
│ │ ├── address_test.go
│ │ ├── constants.go
│ │ ├── errors.go
│ │ ├── instruction_burndustwithauthority.go
│ │ ├── instruction_closeaccounts.go
│ │ ├── instruction_deactivate.go
│ │ ├── instruction_initialize.go
│ │ ├── instruction_revokelockwithauthority.go
│ │ ├── instruction_withdraw.go
│ │ ├── legacy.go
│ │ ├── legacy_test.go
│ │ ├── program.go
│ │ ├── types.go
│ │ └── utils.go
│ └── v1
│ │ ├── accounts.go
│ │ ├── address.go
│ │ ├── address_test.go
│ │ ├── constants.go
│ │ ├── errors.go
│ │ ├── instruction_activate.go
│ │ ├── instruction_burndustwithauthority.go
│ │ ├── instruction_cancellocktimeout.go
│ │ ├── instruction_closeAccounts.go
│ │ ├── instruction_deactivate.go
│ │ ├── instruction_initialize.go
│ │ ├── instruction_revokelockwithauthority.go
│ │ ├── instruction_revokelockwithtimeout.go
│ │ ├── instruction_transferwithauthority.go
│ │ ├── instruction_withdraw.go
│ │ ├── legacy.go
│ │ ├── legacy_test.go
│ │ ├── program.go
│ │ ├── types.go
│ │ └── utils.go
├── token
│ ├── associated.go
│ ├── associated_test.go
│ ├── client.go
│ ├── program.go
│ ├── program_test.go
│ ├── state.go
│ └── state_test.go
├── transaction.go
└── transaction_test.go
├── sync
├── ring.go
├── ring_test.go
├── striped_channel.go
├── striped_channel_test.go
├── striped_lock.go
└── striped_lock_test.go
├── testutil
├── error.go
├── logging.go
├── server.go
├── solana.go
├── subsidizer.go
├── wait.go
└── wait_test.go
├── usdc
└── usdc.go
├── usdg
└── usdg.go
└── usdt
└── usdt.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 |
23 | # Build output directories
24 | build/
25 |
26 | # Visual Studio Code
27 | .vscode/
28 |
29 | # Jetbrains
30 | .idea/
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Code Inc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all
2 | all: test
3 |
4 | .PHONY: test
5 | test:
6 | @go test -cover ./...
7 |
8 |
9 | .PHONY: integration-test
10 | integration-test:
11 | @go test -tags integration -cover -timeout=5m ./...
12 |
13 |
14 | .PHONY: clean-integration-containers
15 | clean-integration-containers:
16 | @echo Removing etcd containers...
17 | @docker ps | grep -E "etcd-test-[0-9a-z]{8}-[0-9]+" | awk '{print $$1}' | xargs docker rm -f 2>/dev/null || true
18 | @echo Removing etcd cluster networks...
19 | @docker network ls | grep -E "etcd-test-[0-9a-z]{8}-network" | awk '{print $$1}' | xargs docker network remove 2>/dev/null || true
20 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security and Issue Disclosures
2 |
3 | In the interest of protecting the security of our users and their funds, we ask
4 | that if you discover any security vulnerabilities in the Code SDK, the clients,
5 | the packages, the on-chain smart contracts, or the mobile app, you report them
6 | using the following proceedure. Our security team will review your report. Your
7 | cooperation in maintaining the security of our products is appreciated.
8 |
9 | ⚠️ **DO NOT CREATE A GITHUB ISSUE** to report a security problem
10 |
11 | # Security Policy
12 |
13 | 1. [Reporting security problems](#reporting)
14 | 1. [Security Bug Bounties](#bounty)
15 | 1. [Incident Response Process](#process)
16 |
17 |
18 | ## Reporting security problems in the Code Program Library
19 |
20 | Please use this [Report a Vulnerability](https://github.com/stickychin/code-server/security/advisories/new) link.
21 | Provide a helpful title and detailed description of the problem.
22 |
23 | If you haven't done so already, please **enable two-factor auth** in your GitHub account.
24 |
25 | Expect a response as fast as possible in the advisory, typically within 72 hours.
26 |
27 |
28 | ## Bounty
29 |
30 | Code Inc may offer bounties for critical security issues. Either a demonstration
31 | or a valid bug report is all that's necessary to submit a bug bounty. A patch to
32 | fix the issue isn't required.
33 |
34 |
35 | ## Process
36 |
37 | If you do not receive a response in the advisory, send an email to
38 | security@getcode.com with the full URL of the advisory you have created. DO NOT
39 | include attachments or provide detail sufficient for exploitation regarding the
40 | security issue in this email. **Only provide such details in the advisory**.
41 |
42 | If you do not receive a response from security@getcode.com please followup with
43 | the team directly.
44 |
--------------------------------------------------------------------------------
/pkg/cluster/doc.go:
--------------------------------------------------------------------------------
1 | // Package cluster provides utilities for multi-server clustering. It allows nodes
2 | // to coordinate a shared membership state, which can in turn be used to build
3 | // higher level features, such as Distributed Nonce Pools and RPC routing.
4 | //
5 | // In general, an application will create a Cluster, using CreateMembership() to
6 | // register the current process in the cluster. The data of each Member is
7 | // arbitrary, and can be used by the higher level application to coordinate state.
8 | //
9 | // Multiple clusters can be created in a given server process. However, multiple
10 | // Cluster instances for the same real cluster should be avoided.
11 | package cluster
12 |
--------------------------------------------------------------------------------
/pkg/cluster/grpc/context.go:
--------------------------------------------------------------------------------
1 | package grpc
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type contextKey int
8 |
9 | const (
10 | contextKeyRouting contextKey = iota
11 | contextKeyNode
12 | )
13 |
14 | func WithRoutingKey(parent context.Context, val []byte) context.Context {
15 | return context.WithValue(parent, contextKeyRouting, val)
16 | }
17 |
18 | func RoutingKey(ctx context.Context) (val []byte, ok bool) {
19 | val, ok = ctx.Value(contextKeyRouting).([]byte)
20 | return
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/cluster/memory/memory_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | _ "net/http/pprof"
8 |
9 | "github.com/stickychin/code-server/pkg/cluster"
10 | clustertests "github.com/stickychin/code-server/pkg/cluster/tests"
11 | )
12 |
13 | func TestMemory(t *testing.T) {
14 | go func() {
15 | http.ListenAndServe(":6060", nil)
16 | }()
17 |
18 | clustertests.RunClusterTests(t, func() (cluster.Cluster, error) {
19 | return NewCluster(), nil
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/code/aml/metrics.go:
--------------------------------------------------------------------------------
1 | package aml
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/metrics"
7 | )
8 |
9 | const (
10 | metricsStructName = "aml.guard"
11 |
12 | eventName = "AntiMoneyLaunderingGuardDenial"
13 | )
14 |
15 | func recordDenialEvent(ctx context.Context, reason string) {
16 | kvPairs := map[string]interface{}{
17 | "reason": reason,
18 | "count": 1,
19 | }
20 | metrics.RecordEvent(ctx, eventName, kvPairs)
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/code/antispam/metrics.go:
--------------------------------------------------------------------------------
1 | package antispam
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/metrics"
7 | )
8 |
9 | const (
10 | metricsStructName = "antispam.guard"
11 |
12 | eventName = "AntispamGuardDenial"
13 |
14 | actionOpenAccounts = "OpenAccounts"
15 | actionSendPayment = "SendPayment"
16 | actionReceivePayments = "ReceivePayments"
17 |
18 | actionWelcomeBonus = "WelcomeBonus"
19 |
20 | actionSwap = "Swap"
21 | )
22 |
23 | func recordDenialEvent(ctx context.Context, action, reason string) {
24 | kvPairs := map[string]interface{}{
25 | "action": action,
26 | "reason": reason,
27 | "count": 1,
28 | }
29 | metrics.RecordEvent(ctx, eventName, kvPairs)
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/code/async/account/config.go:
--------------------------------------------------------------------------------
1 | package async_account
2 |
3 | // todo: setup configs
4 |
5 | const (
6 | envConfigPrefix = "ACCOUNT_SERVICE_"
7 | )
8 |
9 | type conf struct {
10 | }
11 |
12 | // ConfigProvider defines how config values are pulled
13 | type ConfigProvider func() *conf
14 |
15 | // WithEnvConfigs returns configuration pulled from environment variables
16 | func WithEnvConfigs() ConfigProvider {
17 | return func() *conf {
18 | return &conf{}
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/code/async/account/metrics.go:
--------------------------------------------------------------------------------
1 | package async_account
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/stickychin/code-server/pkg/metrics"
8 | )
9 |
10 | const (
11 | giftCardWorkerEventName = "GiftCardWorkerPollingCheck"
12 | swapRetryWorkerEventName = "SwapRetryWorkerPollingCheck"
13 | )
14 |
15 | func (p *service) metricsGaugeWorker(ctx context.Context) error {
16 | delay := time.Second
17 |
18 | for {
19 | select {
20 | case <-ctx.Done():
21 | return ctx.Err()
22 | case <-time.After(delay):
23 | start := time.Now()
24 |
25 | p.recordBackupQueueStatusPollingEvent(ctx)
26 |
27 | delay = time.Second - time.Since(start)
28 | }
29 | }
30 | }
31 |
32 | func (p *service) recordBackupQueueStatusPollingEvent(ctx context.Context) {
33 | count, err := p.data.GetAccountInfoCountRequiringAutoReturnCheck(ctx)
34 | if err == nil {
35 | metrics.RecordEvent(ctx, giftCardWorkerEventName, map[string]interface{}{
36 | "queue_size": count,
37 | })
38 | }
39 |
40 | count, err = p.data.GetAccountInfoCountRequiringSwapRetry(ctx)
41 | if err == nil {
42 | metrics.RecordEvent(ctx, swapRetryWorkerEventName, map[string]interface{}{
43 | "queue_size": count,
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/code/async/account/service.go:
--------------------------------------------------------------------------------
1 | package async_account
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/sirupsen/logrus"
8 |
9 | "github.com/stickychin/code-server/pkg/code/async"
10 | code_data "github.com/stickychin/code-server/pkg/code/data"
11 | )
12 |
13 | type service struct {
14 | log *logrus.Entry
15 | conf *conf
16 | data code_data.Provider
17 | }
18 |
19 | func New(data code_data.Provider, configProvider ConfigProvider) async.Service {
20 | return &service{
21 | log: logrus.StandardLogger().WithField("service", "account"),
22 | conf: configProvider(),
23 | data: data,
24 | }
25 | }
26 |
27 | func (p *service) Start(ctx context.Context, interval time.Duration) error {
28 |
29 | go func() {
30 | err := p.giftCardAutoReturnWorker(ctx, interval)
31 | if err != nil && err != context.Canceled {
32 | p.log.WithError(err).Warn("gift card auto-return processing loop terminated unexpectedly")
33 | }
34 | }()
35 |
36 | // todo: the open code protocol needs to get the push token from the implementing app
37 | /*
38 | go func() {
39 | err := p.swapRetryWorker(ctx, interval)
40 | if err != nil && err != context.Canceled {
41 | p.log.WithError(err).Warn("swap retry processing loop terminated unexpectedly")
42 | }
43 | }()
44 | */
45 |
46 | go func() {
47 | err := p.metricsGaugeWorker(ctx)
48 | if err != nil && err != context.Canceled {
49 | p.log.WithError(err).Warn("account metrics gauge loop terminated unexpectedly")
50 | }
51 | }()
52 |
53 | select {
54 | case <-ctx.Done():
55 | return ctx.Err()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/code/async/account/swap_test.go:
--------------------------------------------------------------------------------
1 | package async_account
2 |
3 | // todo: add tests
4 |
--------------------------------------------------------------------------------
/pkg/code/async/airdrop/config.go:
--------------------------------------------------------------------------------
1 | package async_airdrop
2 |
3 | import (
4 | "github.com/stickychin/code-server/pkg/config"
5 | "github.com/stickychin/code-server/pkg/config/env"
6 | )
7 |
8 | // todo: setup configs
9 |
10 | const (
11 | envConfigPrefix = "AIRDROP_SERVICE_"
12 |
13 | DisableAirdropsConfigEnvName = envConfigPrefix + "DISABLE_AIRDROPS"
14 | defaultDisableAirdrops = false
15 |
16 | AirdropperOwnerConfigEnvName = envConfigPrefix + "AIRDROPPER_OWNER"
17 | defaultAirdropperOwner = "invalid" // ensure something valid is set
18 |
19 | NonceMemoryAccountConfigEnvName = envConfigPrefix + "NONCE_MEMORY_ACCOUNT"
20 | defaultNonceMemoryAccount = "invalid" // ensure something valid is set
21 | )
22 |
23 | type conf struct {
24 | disableAirdrops config.Bool
25 | airdropperOwner config.String
26 | nonceMemoryAccount config.String
27 | }
28 |
29 | // ConfigProvider defines how config values are pulled
30 | type ConfigProvider func() *conf
31 |
32 | // WithEnvConfigs returns configuration pulled from environment variables
33 | func WithEnvConfigs() ConfigProvider {
34 | return func() *conf {
35 | return &conf{
36 | disableAirdrops: env.NewBoolConfig(DisableAirdropsConfigEnvName, defaultDisableAirdrops),
37 | airdropperOwner: env.NewStringConfig(AirdropperOwnerConfigEnvName, defaultAirdropperOwner),
38 | nonceMemoryAccount: env.NewStringConfig(NonceMemoryAccountConfigEnvName, defaultNonceMemoryAccount),
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/code/async/airdrop/indexer.go:
--------------------------------------------------------------------------------
1 | package async_airdrop
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/pkg/errors"
7 |
8 | indexerpb "github.com/code-payments/code-vm-indexer/generated/indexer/v1"
9 |
10 | "github.com/stickychin/code-server/pkg/code/common"
11 | )
12 |
13 | var (
14 | errNotIndexed = errors.New("virtual account is not indexed")
15 | )
16 |
17 | func (p *service) getVirtualTimelockAccountMemoryLocation(ctx context.Context, vm, owner *common.Account) (*common.Account, uint16, error) {
18 | resp, err := p.vmIndexerClient.GetVirtualTimelockAccounts(ctx, &indexerpb.GetVirtualTimelockAccountsRequest{
19 | VmAccount: &indexerpb.Address{Value: vm.PublicKey().ToBytes()},
20 | Owner: &indexerpb.Address{Value: owner.PublicKey().ToBytes()},
21 | })
22 | if err != nil {
23 | return nil, 0, err
24 | } else if resp.Result == indexerpb.GetVirtualTimelockAccountsResponse_NOT_FOUND {
25 | return nil, 0, errNotIndexed
26 | } else if resp.Result != indexerpb.GetVirtualTimelockAccountsResponse_OK {
27 | return nil, 0, errors.Errorf("received rpc result %s", resp.Result.String())
28 | }
29 |
30 | if len(resp.Items) > 1 {
31 | return nil, 0, errors.New("multiple results returned")
32 | } else if resp.Items[0].Storage.GetMemory() == nil {
33 | return nil, 0, errors.New("account is compressed")
34 | }
35 |
36 | protoMemory := resp.Items[0].Storage.GetMemory()
37 | memory, err := common.NewAccountFromPublicKeyBytes(protoMemory.Account.Value)
38 | if err != nil {
39 | return nil, 0, err
40 | }
41 | return memory, uint16(protoMemory.Index), nil
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/code/async/airdrop/integration.go:
--------------------------------------------------------------------------------
1 | package async_airdrop
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/code/common"
7 | )
8 |
9 | type Integration interface {
10 | // GetOwnersToAirdropNow gets a set of owner accounts to airdrop right now,
11 | // and the amount that should be airdropped.
12 | GetOwnersToAirdropNow(ctx context.Context) ([]*common.Account, uint64, error)
13 |
14 | // OnSuccess is called when an airdrop completes
15 | OnSuccess(ctx context.Context, owners ...*common.Account) error
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/code/async/airdrop/nonce.go:
--------------------------------------------------------------------------------
1 | package async_airdrop
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | "github.com/stickychin/code-server/pkg/solana/cvm"
8 | )
9 |
10 | func (p *service) refreshNonceMemoryAccountState(ctx context.Context) error {
11 | p.nonceMu.Lock()
12 | defer p.nonceMu.Unlock()
13 |
14 | ai, err := p.data.GetBlockchainAccountInfo(ctx, p.nonceMemoryAccount.PublicKey().ToBase58(), solana.CommitmentFinalized)
15 | if err != nil {
16 | return err
17 | }
18 | return p.nonceMemoryAccountState.Unmarshal(ai.Data)
19 | }
20 |
21 | func (p *service) getVdn() (*cvm.VirtualDurableNonce, uint16, error) {
22 | p.nonceMu.Lock()
23 | defer p.nonceMu.Unlock()
24 |
25 | vaData, _ := p.nonceMemoryAccountState.Data.Read(int(p.nextNonceIndex))
26 | var vdn cvm.VirtualDurableNonce
27 | err := vdn.UnmarshalFromMemory(vaData)
28 | if err != nil {
29 | return nil, 0, err
30 | }
31 | index := p.nextNonceIndex
32 | p.nextNonceIndex = (p.nextNonceIndex + 1) % uint16(len(p.nonceMemoryAccountState.Data.State))
33 | return &vdn, index, nil
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/code/async/airdrop/worker.go:
--------------------------------------------------------------------------------
1 | package async_airdrop
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/newrelic/go-agent/v3/newrelic"
8 |
9 | "github.com/stickychin/code-server/pkg/metrics"
10 | )
11 |
12 | func (p *service) airdropWorker(serviceCtx context.Context, interval time.Duration) error {
13 | delay := time.After(interval)
14 |
15 | for {
16 | select {
17 | case <-delay:
18 | nr := serviceCtx.Value(metrics.NewRelicContextKey).(*newrelic.Application)
19 | m := nr.StartTransaction("async__airdrop_service__airdrop")
20 | defer m.End()
21 | tracedCtx := newrelic.NewContext(serviceCtx, m)
22 |
23 | start := time.Now()
24 |
25 | err := p.doAirdrop(tracedCtx)
26 | if err != nil {
27 | m.NoticeError(err)
28 | }
29 |
30 | delay = time.After(interval - time.Since(start))
31 | case <-serviceCtx.Done():
32 | return serviceCtx.Err()
33 | }
34 | }
35 | }
36 |
37 | func (p *service) doAirdrop(ctx context.Context) error {
38 | log := p.log.WithField("method", "doAirdrop")
39 |
40 | err := p.refreshNonceMemoryAccountState(ctx)
41 | if err != nil {
42 | log.WithError(err).Warn("failed to refresh nonce memory account state")
43 | return err
44 | }
45 |
46 | owners, amount, err := p.integration.GetOwnersToAirdropNow(ctx)
47 | if err != nil {
48 | log.WithError(err).Warn("failed to get owners to airdrop to")
49 | return err
50 | }
51 |
52 | if len(owners) == 0 {
53 | return nil
54 | }
55 |
56 | err = p.airdropToOwners(ctx, amount, owners...)
57 | if err != nil {
58 | log.WithError(err).Warn("failed to airdrop to owners")
59 | return err
60 | }
61 |
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/code/async/geyser/api/Makefile:
--------------------------------------------------------------------------------
1 | all: generate
2 |
3 | generate:
4 | docker run --rm -v $(PWD)/proto:/proto -v $(PWD)/gen:/genproto code-protobuf-api-builder-go
5 |
6 | .PHONY: all generate
--------------------------------------------------------------------------------
/pkg/code/async/geyser/handler_test.go:
--------------------------------------------------------------------------------
1 | package async_geyser
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // todo: implement me
8 | func TestTokenProgramAccountHandler(t *testing.T) {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/pkg/code/async/geyser/retry.go:
--------------------------------------------------------------------------------
1 | package async_geyser
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "time"
7 |
8 | "github.com/stickychin/code-server/pkg/retry"
9 | "github.com/stickychin/code-server/pkg/retry/backoff"
10 | )
11 |
12 | var (
13 | errSignatureNotConfirmed = errors.New("signature is not confirmed")
14 | errSignatureNotFinalized = errors.New("signature is not finalized")
15 | )
16 |
17 | var waitForFinalizationRetryStrategies = []retry.Strategy{
18 | retry.NonRetriableErrors(context.Canceled),
19 | retry.Limit(30),
20 | retry.Backoff(backoff.Constant(3*time.Second), 3*time.Second),
21 | }
22 |
23 | var waitForConfirmationRetryStrategies = []retry.Strategy{
24 | retry.NonRetriableErrors(context.Canceled),
25 | retry.Limit(10),
26 | retry.Backoff(backoff.Constant(time.Second), time.Second),
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/code/async/geyser/util.go:
--------------------------------------------------------------------------------
1 | package async_geyser
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/pkg/errors"
7 |
8 | "github.com/stickychin/code-server/pkg/cache"
9 | "github.com/stickychin/code-server/pkg/code/common"
10 | code_data "github.com/stickychin/code-server/pkg/code/data"
11 | "github.com/stickychin/code-server/pkg/code/data/timelock"
12 | )
13 |
14 | var (
15 | depositPdaToUserAuthorityCache = cache.NewCache(1_000_000)
16 | )
17 |
18 | // todo: use a bloom filter, but a caching strategy might be ok for now
19 | func testForKnownUserAuthorityFromDepositPda(ctx context.Context, data code_data.Provider, depositPdaAccount *common.Account) (bool, *common.Account, error) {
20 | cached, ok := depositPdaToUserAuthorityCache.Retrieve(depositPdaAccount.PublicKey().ToBase58())
21 | if ok {
22 | userAuthorityAccountPublicKeyString := cached.(string)
23 | if len(userAuthorityAccountPublicKeyString) > 0 {
24 | userAuthorityAccount, _ := common.NewAccountFromPublicKeyString(userAuthorityAccountPublicKeyString)
25 | return true, userAuthorityAccount, nil
26 | }
27 | return false, nil, nil
28 | }
29 |
30 | timelockRecord, err := data.GetTimelockByDepositPda(ctx, depositPdaAccount.PublicKey().ToBase58())
31 | switch err {
32 | case timelock.ErrTimelockNotFound:
33 | depositPdaToUserAuthorityCache.Insert(depositPdaAccount.PublicKey().ToBase58(), "", 1)
34 | return false, nil, nil
35 | case nil:
36 | userAuthorityAccount, err := common.NewAccountFromPublicKeyString(timelockRecord.VaultOwner)
37 | if err != nil {
38 | return false, nil, errors.New("invalid vault owner account")
39 | }
40 | depositPdaToUserAuthorityCache.Insert(depositPdaAccount.PublicKey().ToBase58(), userAuthorityAccount.PublicKey().ToBase58(), 1)
41 | return true, userAuthorityAccount, nil
42 | default:
43 | return false, nil, errors.Wrap(err, "error getting timelock record")
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/code/async/nonce/config.go:
--------------------------------------------------------------------------------
1 | package async_nonce
2 |
3 | import (
4 | "github.com/stickychin/code-server/pkg/config"
5 | "github.com/stickychin/code-server/pkg/config/env"
6 | )
7 |
8 | const (
9 | envConfigPrefix = "NONCE_SERVICE_"
10 |
11 | solanaMainnetNoncePubkeyPrefixConfigEnvName = envConfigPrefix + "SOLANA_MAINNET_NONCE_PUBKEY_PREFIX"
12 | defaultSolanaMainnetNoncePubkeyPrefix = "non"
13 |
14 | solanaMainnetNoncePoolSizeConfigEnvName = envConfigPrefix + "SOLANA_MAINNET_NONCE_POOL_SIZE"
15 | defaultSolanaMainnetNoncePoolSize = 1000
16 | )
17 |
18 | type conf struct {
19 | solanaMainnetNoncePubkeyPrefix config.String
20 | solanaMainnetNoncePoolSize config.Uint64
21 | }
22 |
23 | // ConfigProvider defines how config values are pulled
24 | type ConfigProvider func() *conf
25 |
26 | // WithEnvConfigs returns configuration pulled from environment variables
27 | func WithEnvConfigs() ConfigProvider {
28 | return func() *conf {
29 | return &conf{
30 | solanaMainnetNoncePubkeyPrefix: env.NewStringConfig(solanaMainnetNoncePubkeyPrefixConfigEnvName, defaultSolanaMainnetNoncePubkeyPrefix),
31 | solanaMainnetNoncePoolSize: env.NewUint64Config(solanaMainnetNoncePoolSizeConfigEnvName, defaultSolanaMainnetNoncePoolSize),
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/code/async/nonce/pool_test.go:
--------------------------------------------------------------------------------
1 | package async_nonce
2 |
--------------------------------------------------------------------------------
/pkg/code/async/sequencer/timelock.go:
--------------------------------------------------------------------------------
1 | package async_sequencer
2 |
3 | import (
4 | "context"
5 |
6 | code_data "github.com/stickychin/code-server/pkg/code/data"
7 | "github.com/stickychin/code-server/pkg/code/data/timelock"
8 | timelock_token_v1 "github.com/stickychin/code-server/pkg/solana/timelock/v1"
9 | )
10 |
11 | // The faster we can update timelock state, the better it is to unblock scheduling.
12 | // Particularly, we don't want a missed Geyser account update to block scheduling.
13 | // Generally, these are very safe if they're used when we first create and close
14 | // accounts, because they denote the initial and end states.
15 |
16 | func markTimelockLocked(ctx context.Context, data code_data.Provider, vault string, slot uint64) error {
17 | record, err := data.GetTimelockByVault(ctx, vault)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | record.VaultState = timelock_token_v1.StateLocked
23 | record.Block = slot
24 |
25 | err = data.SaveTimelock(ctx, record)
26 | if err == timelock.ErrStaleTimelockState {
27 | return nil
28 | }
29 | return err
30 | }
31 |
32 | func markTimelockClosed(ctx context.Context, data code_data.Provider, vault string, slot uint64) error {
33 | record, err := data.GetTimelockByVault(ctx, vault)
34 | if err != nil {
35 | return err
36 | }
37 |
38 | record.VaultState = timelock_token_v1.StateClosed
39 | if record.Block > slot {
40 | // Potential conflict with unlock state detection, force a move to close at the next block
41 | //
42 | // todo: Better way of handling this
43 | record.Block += 1
44 | } else {
45 | record.Block = slot
46 | }
47 |
48 | return data.SaveTimelock(ctx, record)
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/code/async/service.go:
--------------------------------------------------------------------------------
1 | package async
2 |
3 | import (
4 | "context"
5 | "time"
6 | )
7 |
8 | type Service interface {
9 | Start(ctx context.Context, interval time.Duration) error
10 | }
11 |
--------------------------------------------------------------------------------
/pkg/code/async/vm/service.go:
--------------------------------------------------------------------------------
1 | package async_vm
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/sirupsen/logrus"
8 |
9 | "github.com/stickychin/code-server/pkg/code/async"
10 | code_data "github.com/stickychin/code-server/pkg/code/data"
11 | )
12 |
13 | type service struct {
14 | log *logrus.Entry
15 | data code_data.Provider
16 | }
17 |
18 | func New(data code_data.Provider) async.Service {
19 | return &service{
20 | log: logrus.StandardLogger().WithField("service", "vm"),
21 | data: data,
22 | }
23 | }
24 |
25 | func (p *service) Start(ctx context.Context, interval time.Duration) error {
26 | go func() {
27 | err := p.storageInitWorker(ctx, interval)
28 | if err != nil && err != context.Canceled {
29 | p.log.WithError(err).Warn("storage init processing loop terminated unexpectedly")
30 | }
31 | }()
32 |
33 | select {
34 | case <-ctx.Done():
35 | return ctx.Err()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/code/async/webhook/config.go:
--------------------------------------------------------------------------------
1 | package async_webhook
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/stickychin/code-server/pkg/config"
7 | "github.com/stickychin/code-server/pkg/config/env"
8 | "github.com/stickychin/code-server/pkg/config/memory"
9 | "github.com/stickychin/code-server/pkg/config/wrapper"
10 | )
11 |
12 | const (
13 | envConfigPrefix = "WEBHOOK_SERVICE_"
14 |
15 | WorkerBatchSizeConfigEnvName = envConfigPrefix + "WORKER_BATCH_SIZE"
16 | defaultWorkerBatchSize = 250
17 |
18 | WebhookTimeoutConfigEnvName = envConfigPrefix + "WEBHOOK_TIMEOUT"
19 | defaultWebhookTimeout = 3 * time.Second
20 | )
21 |
22 | type conf struct {
23 | workerBatchSize config.Uint64
24 | webhookTimeout config.Duration
25 | }
26 |
27 | // ConfigProvider defines how config values are pulled
28 | type ConfigProvider func() *conf
29 |
30 | // WithEnvConfigs returns configuration pulled from environment variables
31 | func WithEnvConfigs() ConfigProvider {
32 | return func() *conf {
33 | return &conf{
34 | workerBatchSize: env.NewUint64Config(WorkerBatchSizeConfigEnvName, defaultWorkerBatchSize),
35 | webhookTimeout: env.NewDurationConfig(WebhookTimeoutConfigEnvName, defaultWebhookTimeout),
36 | }
37 | }
38 | }
39 |
40 | type testOverrides struct {
41 | }
42 |
43 | func withManualTestOverrides(overrides *testOverrides) ConfigProvider {
44 | return func() *conf {
45 | return &conf{
46 | workerBatchSize: wrapper.NewUint64Config(memory.NewConfig(defaultWorkerBatchSize), defaultWorkerBatchSize),
47 | webhookTimeout: wrapper.NewDurationConfig(memory.NewConfig(defaultWebhookTimeout), defaultWebhookTimeout),
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/code/async/webhook/metrics.go:
--------------------------------------------------------------------------------
1 | package async_webhook
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/stickychin/code-server/pkg/metrics"
8 | "github.com/stickychin/code-server/pkg/code/data/webhook"
9 | )
10 |
11 | const (
12 | webhookCountEventName = "WebhookCountPollingCheck"
13 | webhookCallsEventName = "WebhookCallsPollingCheck"
14 | )
15 |
16 | func (p *service) metricsGaugeWorker(ctx context.Context) error {
17 | delay := time.Second
18 |
19 | for {
20 | select {
21 | case <-ctx.Done():
22 | return ctx.Err()
23 | case <-time.After(delay):
24 | start := time.Now()
25 |
26 | p.recordWebhookCountEvent(ctx, webhook.StatePending)
27 | p.recordWebhookCallsEvents(ctx)
28 |
29 | delay = time.Second - time.Since(start)
30 | }
31 | }
32 | }
33 |
34 | func (p *service) recordWebhookCountEvent(ctx context.Context, state webhook.State) {
35 | count, err := p.data.CountWebhookByState(ctx, state)
36 | if err != nil {
37 | return
38 | }
39 |
40 | metrics.RecordEvent(ctx, webhookCountEventName, map[string]interface{}{
41 | "count": count,
42 | "state": state.String(),
43 | })
44 | }
45 |
46 | func (p *service) recordWebhookCallsEvents(ctx context.Context) {
47 | p.metricsMu.Lock()
48 | successfulCalls := p.successfulWebhooks
49 | failedCalls := p.failedWebhooks
50 | p.successfulWebhooks = 0
51 | p.failedWebhooks = 0
52 | p.metricsMu.Unlock()
53 |
54 | metrics.RecordEvent(ctx, webhookCallsEventName, map[string]interface{}{
55 | "successes": successfulCalls,
56 | "failures": failedCalls,
57 | })
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/code/async/webhook/service.go:
--------------------------------------------------------------------------------
1 | package async_webhook
2 |
3 | import (
4 | "context"
5 | "sync"
6 | "time"
7 |
8 | "github.com/sirupsen/logrus"
9 |
10 | "github.com/stickychin/code-server/pkg/code/async"
11 | code_data "github.com/stickychin/code-server/pkg/code/data"
12 | "github.com/stickychin/code-server/pkg/code/server/messaging"
13 | sync_util "github.com/stickychin/code-server/pkg/sync"
14 | )
15 |
16 | type service struct {
17 | log *logrus.Entry
18 | conf *conf
19 | data code_data.Provider
20 | messagingClient messaging.InternalMessageClient
21 | webhookLocks *sync_util.StripedLock // todo: distributed lock
22 |
23 | metricsMu sync.Mutex
24 | successfulWebhooks int
25 | failedWebhooks int
26 | }
27 |
28 | func New(data code_data.Provider, messagingClient messaging.InternalMessageClient, configProvider ConfigProvider) async.Service {
29 | return &service{
30 | log: logrus.StandardLogger().WithField("service", "webhook"),
31 | conf: configProvider(),
32 | data: data,
33 | messagingClient: messagingClient,
34 | webhookLocks: sync_util.NewStripedLock(1024),
35 | }
36 | }
37 |
38 | func (p *service) Start(ctx context.Context, interval time.Duration) error {
39 | go func() {
40 | err := p.worker(ctx, interval)
41 | if err != nil && err != context.Canceled {
42 | p.log.WithError(err).Warnf("webhook processing loop terminated unexpectedly")
43 | }
44 | }()
45 |
46 | go func() {
47 | err := p.metricsGaugeWorker(ctx)
48 | if err != nil && err != context.Canceled {
49 | p.log.WithError(err).Warn("webhook metrics gauge loop terminated unexpectedly")
50 | }
51 | }()
52 |
53 | select {
54 | case <-ctx.Done():
55 | return ctx.Err()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/code/common/key.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/mr-tron/base58"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | type Key struct {
11 | bytesValue []byte
12 | stringValue string
13 | }
14 |
15 | func NewKeyFromBytes(value []byte) (*Key, error) {
16 | k := &Key{
17 | bytesValue: value,
18 | stringValue: base58.Encode(value),
19 | }
20 |
21 | if err := k.Validate(); err != nil {
22 | return nil, err
23 | }
24 | return k, nil
25 | }
26 |
27 | func NewKeyFromString(value string) (*Key, error) {
28 | bytesValue, err := base58.Decode(value)
29 | if err != nil {
30 | return nil, errors.Wrap(err, "error decoding string as base58")
31 | }
32 |
33 | k := &Key{
34 | bytesValue: bytesValue,
35 | stringValue: value,
36 | }
37 |
38 | if err := k.Validate(); err != nil {
39 | return nil, err
40 | }
41 | return k, nil
42 | }
43 |
44 | func NewRandomKey() (*Key, error) {
45 | _, privateKeyBytes, err := ed25519.GenerateKey(nil)
46 | if err != nil {
47 | return nil, errors.Wrap(err, "error generating private key")
48 | }
49 |
50 | return NewKeyFromBytes(privateKeyBytes)
51 | }
52 |
53 | func (k *Key) ToBytes() []byte {
54 | return k.bytesValue
55 | }
56 |
57 | func (k *Key) ToBase58() string {
58 | return k.stringValue
59 | }
60 |
61 | func (k *Key) IsPublic() bool {
62 | return len(k.bytesValue) != ed25519.PrivateKeySize
63 | }
64 |
65 | func (k *Key) Validate() error {
66 | if k == nil {
67 | return errors.New("key is nil")
68 | }
69 |
70 | if len(k.bytesValue) != ed25519.PublicKeySize && len(k.bytesValue) != ed25519.PrivateKeySize {
71 | return errors.New("key must be an ed25519 public or private key")
72 | }
73 |
74 | if base58.Encode(k.bytesValue) != k.stringValue {
75 | return errors.New("bytes and string representation don't match")
76 | }
77 |
78 | return nil
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/code/common/key_test.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "crypto/ed25519"
5 | "testing"
6 |
7 | "github.com/mr-tron/base58"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestPublicKey(t *testing.T) {
13 | publicKey, _, err := ed25519.GenerateKey(nil)
14 | require.NoError(t, err)
15 |
16 | var keys []*Key
17 |
18 | key, err := NewKeyFromBytes(publicKey)
19 | require.NoError(t, err)
20 | keys = append(keys, key)
21 |
22 | key, err = NewKeyFromString(base58.Encode(publicKey))
23 | require.NoError(t, err)
24 | keys = append(keys, key)
25 |
26 | for _, key := range keys {
27 | assert.True(t, key.IsPublic())
28 | assert.EqualValues(t, publicKey, key.ToBytes())
29 | assert.Equal(t, base58.Encode(publicKey), key.ToBase58())
30 | }
31 | }
32 |
33 | func TestPrivateKey(t *testing.T) {
34 | _, privateKey, err := ed25519.GenerateKey(nil)
35 | require.NoError(t, err)
36 |
37 | var keys []*Key
38 |
39 | key, err := NewKeyFromBytes(privateKey)
40 | require.NoError(t, err)
41 | keys = append(keys, key)
42 |
43 | key, err = NewKeyFromString(base58.Encode(privateKey))
44 | require.NoError(t, err)
45 | keys = append(keys, key)
46 |
47 | for _, key := range keys {
48 | assert.False(t, key.IsPublic())
49 | assert.EqualValues(t, privateKey, key.ToBytes())
50 | assert.Equal(t, base58.Encode(privateKey), key.ToBase58())
51 | }
52 | }
53 |
54 | func TestInvalidKey(t *testing.T) {
55 | stringValue := "invalid-key"
56 | bytesValue := []byte(stringValue)
57 |
58 | _, err := NewKeyFromString(stringValue)
59 | assert.Error(t, err)
60 |
61 | _, err = NewKeyFromBytes(bytesValue)
62 | assert.Error(t, err)
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/code/common/mint_test.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestStrToQuarks_HappyPath(t *testing.T) {
11 | for _, tc := range []struct {
12 | input string
13 | expected int64
14 | }{
15 | {"123.456", 123456000},
16 | {"123", 123000000},
17 | {"0.456", 456000},
18 | {"1234567890123.123456", 1234567890123123456},
19 | } {
20 | quarks, err := StrToQuarks(tc.input)
21 | require.NoError(t, err)
22 | assert.Equal(t, tc.expected, quarks)
23 | }
24 | }
25 |
26 | func TestStrToQuarks_InvalidString(t *testing.T) {
27 | for _, tc := range []string{
28 | "",
29 | "abc",
30 | "1.1.1",
31 | "0.1234567",
32 | "12345678901234",
33 | } {
34 | _, err := StrToQuarks(tc)
35 | assert.Error(t, err)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/code/common/testutil.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | // Required because we'd have a dependency loop with the testutil package
10 | func newRandomTestAccount(t *testing.T) *Account {
11 | account, err := NewRandomAccount()
12 | require.NoError(t, err)
13 | return account
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/code/common/vm.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import "github.com/stickychin/code-server/pkg/code/config"
4 |
5 | var (
6 | // The well-known Code VM instance
7 | CodeVmAccount, _ = NewAccountFromPublicKeyString(config.VmAccountPublicKey)
8 |
9 | // The well-known Code VM instance omnibus account
10 | CodeVmOmnibusAccount, _ = NewAccountFromPublicKeyString(config.VmOmnibusPublicKey)
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/code/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/mr-tron/base58"
5 |
6 | currency_lib "github.com/stickychin/code-server/pkg/currency"
7 | "github.com/stickychin/code-server/pkg/usdc"
8 | )
9 |
10 | // todo: more things can be pulled into here to configure the open code protocol
11 | // todo: make these environment configs
12 |
13 | const (
14 | // Random values. Replace with real mint configuration
15 | CoreMintPublicKeyString = "DWYE8SQkpestTvpCxGNTCRjC2E9Kn6TCnu2SxkddrEEU"
16 | CoreMintQuarksPerUnit = uint64(usdc.QuarksPerUsdc)
17 | CoreMintSymbol = currency_lib.USDC
18 | CoreMintDecimals = usdc.Decimals
19 |
20 | // Random value. Replace with real subsidizer public keys
21 | SubsidizerPublicKey = "84ydcM4Yp59W6aZP6eSaKiAMaKidNLfb5k318sT2pm14"
22 |
23 | // Random value. Replace with real VM public keys
24 | VmAccountPublicKey = "BVMGLfRgr3nVFCH5DuW6VR2kfSDxq4EFEopXfwCDpYzb"
25 | VmOmnibusPublicKey = "GNw1t85VH8b1CcwB5933KBC7PboDPJ5EcQdGynbfN1Pb"
26 | )
27 |
28 | var (
29 | CoreMintPublicKeyBytes []byte
30 | )
31 |
32 | func init() {
33 | decoded, err := base58.Decode(CoreMintPublicKeyString)
34 | if err != nil {
35 | panic(err)
36 | }
37 | CoreMintPublicKeyBytes = decoded
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/code/data/account/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/account/tests"
7 | )
8 |
9 | func TestAccountInfoMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/action/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/action/tests"
7 | )
8 |
9 | func TestActionMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/balance/checkpoint.go:
--------------------------------------------------------------------------------
1 | package balance
2 |
3 | import (
4 | "errors"
5 | "time"
6 | )
7 |
8 | // Note: Only supports external balances
9 | type Record struct {
10 | Id uint64
11 |
12 | TokenAccount string
13 | Quarks uint64
14 | SlotCheckpoint uint64
15 |
16 | LastUpdatedAt time.Time
17 | }
18 |
19 | func (r *Record) Validate() error {
20 | if len(r.TokenAccount) == 0 {
21 | return errors.New("token account is required")
22 | }
23 |
24 | return nil
25 | }
26 |
27 | func (r *Record) Clone() Record {
28 | return Record{
29 | Id: r.Id,
30 |
31 | TokenAccount: r.TokenAccount,
32 | Quarks: r.Quarks,
33 | SlotCheckpoint: r.SlotCheckpoint,
34 |
35 | LastUpdatedAt: r.LastUpdatedAt,
36 | }
37 | }
38 |
39 | func (r *Record) CopyTo(dst *Record) {
40 | dst.Id = r.Id
41 |
42 | dst.TokenAccount = r.TokenAccount
43 | dst.Quarks = r.Quarks
44 | dst.SlotCheckpoint = r.SlotCheckpoint
45 |
46 | dst.LastUpdatedAt = r.LastUpdatedAt
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/code/data/balance/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/balance/tests"
7 | )
8 |
9 | func TestBalanceMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/balance/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/balance"
10 | )
11 |
12 | type store struct {
13 | db *sqlx.DB
14 | }
15 |
16 | // New returns a new postgres balance.Store
17 | func New(db *sql.DB) balance.Store {
18 | return &store{
19 | db: sqlx.NewDb(db, "pgx"),
20 | }
21 | }
22 |
23 | // SaveCheckpoint implements balance.Store.SaveCheckpoint
24 | func (s *store) SaveCheckpoint(ctx context.Context, record *balance.Record) error {
25 | model, err := toModel(record)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | if err := model.dbSave(ctx, s.db); err != nil {
31 | return err
32 | }
33 |
34 | res := fromModel(model)
35 | res.CopyTo(record)
36 |
37 | return nil
38 | }
39 |
40 | // GetCheckpoint implements balance.Store.GetCheckpoint
41 | func (s *store) GetCheckpoint(ctx context.Context, account string) (*balance.Record, error) {
42 | model, err := dbGetCheckpoint(ctx, s.db, account)
43 | if err != nil {
44 | return nil, err
45 | }
46 | return fromModel(model), nil
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/code/data/balance/store.go:
--------------------------------------------------------------------------------
1 | package balance
2 |
3 | import (
4 | "context"
5 | "errors"
6 | )
7 |
8 | var (
9 | ErrCheckpointNotFound = errors.New("checkpoint not found")
10 |
11 | ErrStaleCheckpoint = errors.New("checkpoint is stale")
12 | )
13 |
14 | type Store interface {
15 | // SaveCheckpoint saves a balance at a checkpoint. ErrStaleCheckpoint is returned
16 | // if the checkpoint is outdated
17 | SaveCheckpoint(ctx context.Context, record *Record) error
18 |
19 | // GetCheckpoint gets a balance checkpoint for a given account. ErrCheckpointNotFound
20 | // is returend if no DB record exists.
21 | GetCheckpoint(ctx context.Context, account string) (*Record, error)
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/code/data/config.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "github.com/stickychin/code-server/pkg/config"
5 | "github.com/stickychin/code-server/pkg/config/env"
6 | )
7 |
8 | const (
9 | FixerApiKeyConfigEnvName = "FIXER_API_KEY"
10 | defaultFixerApiKey = ""
11 | )
12 |
13 | // todo: Add other data store configs here (eg. postgres, solana, etc).
14 | type conf struct {
15 | fixerApiKey config.String
16 | }
17 |
18 | // ConfigProvider defines how config values are pulled
19 | //
20 | // todo: Possibly introduce an override config provider, for things like scripts.
21 | type ConfigProvider func() *conf
22 |
23 | // WithEnvConfigs returns configuration pulled from environment variables
24 | func WithEnvConfigs() ConfigProvider {
25 | return func() *conf {
26 | return &conf{
27 | fixerApiKey: env.NewStringConfig(FixerApiKeyConfigEnvName, defaultFixerApiKey),
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/code/data/currency/util/util.go:
--------------------------------------------------------------------------------
1 | package currencyutil
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/stickychin/code-server/pkg/code/data/currency"
8 | )
9 |
10 | func PrintTable(records []*currency.ExchangeRateRecord) {
11 | fmt.Println("|index|symbol|time|rate|")
12 | for _, item := range records {
13 | fmt.Printf("|%d|%s|%s|%f|\n",
14 | item.Id,
15 | item.Symbol,
16 | item.Time.Format(time.RFC3339),
17 | item.Rate,
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/ram/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/cvm/ram/tests"
7 | )
8 |
9 | func TestVmRamMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/ram/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/cvm/ram"
10 | "github.com/stickychin/code-server/pkg/solana/cvm"
11 | )
12 |
13 | type store struct {
14 | db *sqlx.DB
15 | }
16 |
17 | // New returns a new postgres cvm.ram.Store
18 | func New(db *sql.DB) ram.Store {
19 | return &store{
20 | db: sqlx.NewDb(db, "pgx"),
21 | }
22 | }
23 |
24 | // InitializeMemory implements cvm.ram.Store.InitializeMemory
25 | func (s *store) InitializeMemory(ctx context.Context, record *ram.Record) error {
26 | model, err := toAccountModel(record)
27 | if err != nil {
28 | return err
29 | }
30 |
31 | err = model.dbInitialize(ctx, s.db)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | res := fromAccountModel(model)
37 | res.CopyTo(record)
38 |
39 | return nil
40 | }
41 |
42 | // FreeMemoryByIndex implements cvm.ram.Store.FreeMemoryByIndex
43 | func (s *store) FreeMemoryByIndex(ctx context.Context, memoryAccount string, index uint16) error {
44 | return dbFreeMemoryByIndex(ctx, s.db, memoryAccount, index)
45 | }
46 |
47 | // FreeMemoryByAddress implements cvm.ram.Store.FreeMemoryByAddress
48 | func (s *store) FreeMemoryByAddress(ctx context.Context, address string) error {
49 | return dbFreeMemoryByAddress(ctx, s.db, address)
50 | }
51 |
52 | // ReserveMemory implements cvm.ram.Store.ReserveMemory
53 | func (s *store) ReserveMemory(ctx context.Context, vm string, accountType cvm.VirtualAccountType, address string) (string, uint16, error) {
54 | return dbReserveMemory(ctx, s.db, vm, accountType, address)
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/ram/store.go:
--------------------------------------------------------------------------------
1 | package ram
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/stickychin/code-server/pkg/solana/cvm"
8 | )
9 |
10 | var (
11 | ErrAlreadyInitialized = errors.New("memory account already initalized")
12 | ErrNoFreeMemory = errors.New("no available free memory")
13 | ErrNotReserved = errors.New("memory is not reserved")
14 | ErrAddressAlreadyReserved = errors.New("virtual account address already in memory")
15 | )
16 |
17 | // Store implements a basic construct for managing RAM memory. For simplicity,
18 | // it is assumed that each memory account will store a single account type,
19 | // which eliminates any complexities with parallel transaction execution resulting
20 | // in allocation errors due to free pages across sectors.
21 | //
22 | // Note: A lock outside this implementation is required to resolve any races.
23 | type Store interface {
24 | // Initializes a memory account for management
25 | InitializeMemory(ctx context.Context, record *Record) error
26 |
27 | // FreeMemoryByIndex frees a piece of memory from a memory account at a particual index
28 | FreeMemoryByIndex(ctx context.Context, memoryAccount string, index uint16) error
29 |
30 | // FreeMemoryByAddress frees a piece of memory assigned to the virtual account address
31 | FreeMemoryByAddress(ctx context.Context, address string) error
32 |
33 | // ReserveMemory reserves a piece of memory in a VM for the virtual account address
34 | ReserveMemory(ctx context.Context, vm string, accountType cvm.VirtualAccountType, address string) (string, uint16, error)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/ram/util.go:
--------------------------------------------------------------------------------
1 | package ram
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/stickychin/code-server/pkg/solana/cvm"
7 | )
8 |
9 | func GetActualCapcity(record *Record) uint16 {
10 | sizeInMemory := int(cvm.GetVirtualAccountSizeInMemory(record.StoredAccountType))
11 | pagesPerAccount := math.Ceil(1 / (float64(record.PageSize) / float64(sizeInMemory)))
12 | availablePerSector := int(record.NumPages) / int(pagesPerAccount)
13 | maxAvailableAcrossSectors := uint16(record.NumSectors) * uint16(availablePerSector)
14 | if record.Capacity < maxAvailableAcrossSectors {
15 | return record.Capacity
16 | }
17 | return maxAvailableAcrossSectors
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/ram/util_test.go:
--------------------------------------------------------------------------------
1 | package ram
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/stickychin/code-server/pkg/solana/cvm"
9 | )
10 |
11 | func TestGetActualCapcity(t *testing.T) {
12 | for _, tc := range []struct {
13 | capacity uint16
14 | numSectors uint16
15 | numPages uint16
16 | pageSize uint8
17 | expected uint16
18 | }{
19 | {
20 | capacity: 1000,
21 | numSectors: 2,
22 | numPages: 50,
23 | pageSize: uint8(cvm.GetVirtualAccountSizeInMemory(cvm.VirtualAccountTypeTimelock)),
24 | expected: 100,
25 | },
26 | {
27 | capacity: 10,
28 | numSectors: 2,
29 | numPages: 50,
30 | pageSize: uint8(cvm.GetVirtualAccountSizeInMemory(cvm.VirtualAccountTypeTimelock)),
31 | expected: 10,
32 | },
33 | {
34 | capacity: 1000,
35 | numSectors: 2,
36 | numPages: 50,
37 | pageSize: uint8(cvm.GetVirtualAccountSizeInMemory(cvm.VirtualAccountTypeTimelock)) - 1,
38 | expected: 50,
39 | },
40 | {
41 | capacity: 1000,
42 | numSectors: 2,
43 | numPages: 50,
44 | pageSize: uint8(cvm.GetVirtualAccountSizeInMemory(cvm.VirtualAccountTypeTimelock)) / 3,
45 | expected: 24,
46 | },
47 | } {
48 | record := &Record{
49 | Capacity: tc.capacity,
50 | NumSectors: tc.numSectors,
51 | NumPages: tc.numPages,
52 | PageSize: tc.pageSize,
53 | StoredAccountType: cvm.VirtualAccountTypeTimelock,
54 | }
55 | actual := GetActualCapcity(record)
56 | assert.Equal(t, tc.expected, actual)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/storage/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/cvm/storage/tests"
7 | )
8 |
9 | func TestVmStorageMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/storage/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/cvm/storage"
10 | )
11 |
12 | type store struct {
13 | db *sqlx.DB
14 | }
15 |
16 | // New returns a new postgres vm.storage.Store
17 | func New(db *sql.DB) storage.Store {
18 | return &store{
19 | db: sqlx.NewDb(db, "pgx"),
20 | }
21 | }
22 |
23 | // InitializeStorage implements vm.storage.Store.InitializeStorage
24 | func (s *store) InitializeStorage(ctx context.Context, record *storage.Record) error {
25 | if record.AvailableCapacity != storage.GetMaxCapacity(record.Levels) {
26 | return storage.ErrInvalidInitialCapacity
27 | }
28 |
29 | model, err := toAccountModel(record)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | err = model.dbInitialize(ctx, s.db)
35 | if err != nil {
36 | return err
37 | }
38 |
39 | fromAccountModel(model).CopyTo(record)
40 |
41 | return nil
42 | }
43 |
44 | // FindAnyWithAvailableCapacity implements cvm.storage.Store.FindAnyWithAvailableCapacity
45 | func (s *store) FindAnyWithAvailableCapacity(ctx context.Context, vm string, purpose storage.Purpose, minCapacity uint64) (*storage.Record, error) {
46 | model, err := dbFindAnyWithAvailableCapacity(ctx, s.db, vm, purpose, minCapacity)
47 | if err != nil {
48 | return nil, err
49 | }
50 | return fromAccountModel(model), nil
51 | }
52 |
53 | // ReserveStorage implements cvm.storage.Store.ReserveStorage
54 | func (s *store) ReserveStorage(ctx context.Context, vm string, purpose storage.Purpose, address string) (string, error) {
55 | return dbReserveStorage(ctx, s.db, vm, purpose, address)
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/code/data/cvm/storage/store.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "errors"
6 | )
7 |
8 | var (
9 | ErrAddressAlreadyReserved = errors.New("virtual account address already in storage")
10 | ErrAlreadyInitialized = errors.New("storage account already initalized")
11 | ErrInvalidInitialCapacity = errors.New("available capacity must be maximum when initializing storage")
12 | ErrNoFreeStorage = errors.New("no available free storage")
13 | ErrNotFound = errors.New("no storage accounts found")
14 | )
15 |
16 | // Store implements a basic construct for managing compression storage.
17 | //
18 | // Note: A lock outside this implementation is required to resolve any races.
19 | type Store interface {
20 | // Initializes a VM storage account for management
21 | InitializeStorage(ctx context.Context, record *Record) error
22 |
23 | // FindAnyWithAvailableCapacity finds a VM storage account with minimum available capcity
24 | FindAnyWithAvailableCapacity(ctx context.Context, vm string, purpose Purpose, minCapacity uint64) (*Record, error)
25 |
26 | // ReserveStorage reserves a piece of storage in a VM for the virtual account address
27 | ReserveStorage(ctx context.Context, vm string, purpose Purpose, address string) (string, error)
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/code/data/deposit/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/deposit/tests"
7 | )
8 |
9 | func TestDepositMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/deposit/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/deposit"
10 | )
11 |
12 | type store struct {
13 | db *sqlx.DB
14 | }
15 |
16 | // New returns a new postgres deposit.Store
17 | func New(db *sql.DB) deposit.Store {
18 | return &store{
19 | db: sqlx.NewDb(db, "pgx"),
20 | }
21 | }
22 |
23 | // Save implements deposit.Store.Save
24 | func (s *store) Save(ctx context.Context, record *deposit.Record) error {
25 | model, err := toModel(record)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | err = model.dbSave(ctx, s.db)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | res := fromModel(model)
36 | res.CopyTo(record)
37 |
38 | return nil
39 | }
40 |
41 | // Get implements deposit.Store.Get
42 | func (s *store) Get(ctx context.Context, signature, account string) (*deposit.Record, error) {
43 | model, err := dbGet(ctx, s.db, signature, account)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return fromModel(model), nil
48 | }
49 |
50 | // GetQuarkAmount implements deposit.Store.GetQuarkAmount
51 | func (s *store) GetQuarkAmount(ctx context.Context, account string) (uint64, error) {
52 | return dbGetQuarkAmount(ctx, s.db, account)
53 | }
54 |
55 | // GetQuarkAmountBatch implements deposit.Store.GetQuarkAmountBatch
56 | func (s *store) GetQuarkAmountBatch(ctx context.Context, accounts ...string) (map[string]uint64, error) {
57 | return dbGetQuarkAmountBatch(ctx, s.db, accounts...)
58 | }
59 |
60 | // GetUsdAmount implements deposit.Store.GetUsdAmount
61 | func (s *store) GetUsdAmount(ctx context.Context, account string) (float64, error) {
62 | return dbGetUsdAmount(ctx, s.db, account)
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/code/data/estimated_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestEstimated_Account(t *testing.T) {
12 | ctx := context.Background()
13 |
14 | provider, err := NewEstimatedProvider()
15 | require.NoError(t, err)
16 |
17 | err = provider.AddKnownAccount(ctx, []byte("account_1"))
18 | require.NoError(t, err)
19 |
20 | err = provider.AddKnownAccount(ctx, []byte("account_2"))
21 | require.NoError(t, err)
22 |
23 | found, err := provider.TestForKnownAccount(ctx, []byte("account_x"))
24 | require.NoError(t, err)
25 | assert.False(t, found)
26 |
27 | found, err = provider.TestForKnownAccount(ctx, []byte("account_1"))
28 | require.NoError(t, err)
29 | assert.True(t, found)
30 |
31 | found, err = provider.TestForKnownAccount(ctx, []byte("account_2"))
32 | require.NoError(t, err)
33 | assert.True(t, found)
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/code/data/external_test.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestComputeAllExchangeRates_HappyPath(t *testing.T) {
11 | coreMintRates := map[string]float64{
12 | "usd": 0.5,
13 | "cad": 1.0,
14 | }
15 |
16 | usdRates := map[string]float64{
17 | "usd": 1.0,
18 | "cad": 1.3,
19 | "eur": 1.0,
20 | "aud": 0.66,
21 | }
22 |
23 | rates, err := computeAllExchangeRates(coreMintRates, usdRates)
24 | require.NoError(t, err)
25 |
26 | assert.Equal(t, rates["usd"], 0.5)
27 | assert.Equal(t, rates["cad"], 0.65)
28 | assert.Equal(t, rates["eur"], 0.5)
29 | assert.Equal(t, rates["aud"], 0.33)
30 | }
31 |
32 | func TestComputeAllExchangeRates_UsdRateMissing(t *testing.T) {
33 | kinRates := map[string]float64{
34 | "cad": 1.0,
35 | }
36 |
37 | usdRates := map[string]float64{
38 | "usd": 1.0,
39 | "cad": 1.3,
40 | "eur": 1.0,
41 | "aud": 0.66,
42 | }
43 |
44 | _, err := computeAllExchangeRates(kinRates, usdRates)
45 | assert.Error(t, err)
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/code/data/fulfillment/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/fulfillment/tests"
7 | )
8 |
9 | func TestFulfillmentMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/intent/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/intent/tests"
7 | )
8 |
9 | func TestIntentMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/intent/store.go:
--------------------------------------------------------------------------------
1 | package intent
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/stickychin/code-server/pkg/database/query"
8 | )
9 |
10 | type Store interface {
11 | // Save creates or updates an intent on the store.
12 | Save(ctx context.Context, record *Record) error
13 |
14 | // Get finds the intent record for a given intent ID.
15 | //
16 | // Returns ErrNotFound if no record is found.
17 | Get(ctx context.Context, intentID string) (*Record, error)
18 |
19 | // GetAllByOwner returns all records for a given owner (as both a source and destination).
20 | //
21 | // Returns ErrNotFound if no records are found.
22 | GetAllByOwner(ctx context.Context, owner string, cursor query.Cursor, limit uint64, direction query.Ordering) ([]*Record, error)
23 |
24 | // GetLatestByInitiatorAndType gets the latest record by initiating owner and intent type
25 | //
26 | // Returns ErrNotFound if no records are found.
27 | GetLatestByInitiatorAndType(ctx context.Context, intentType Type, owner string) (*Record, error)
28 |
29 | // GetOriginalGiftCardIssuedIntent gets the original intent where a gift card
30 | // was issued by its vault address.
31 | GetOriginalGiftCardIssuedIntent(ctx context.Context, giftCardVault string) (*Record, error)
32 |
33 | // GetGiftCardClaimedIntent gets the intent where a gift card was claimed by its
34 | // vault address.
35 | GetGiftCardClaimedIntent(ctx context.Context, giftCardVault string) (*Record, error)
36 |
37 | // GetTransactedAmountForAntiMoneyLaundering gets the total transacted core mint quarks and the
38 | // corresponding USD market value for an owner since a timestamp.
39 | GetTransactedAmountForAntiMoneyLaundering(ctx context.Context, owner string, since time.Time) (uint64, float64, error)
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/code/data/merkletree/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/merkletree/tests"
7 | )
8 |
9 | func TestMerkleTreeMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/messaging/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/messaging/tests"
7 | )
8 |
9 | func TestMessagingMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 |
15 | tests.RunTests(t, testStore, teardown)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/code/data/messaging/postgres/model_test.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "crypto/ed25519"
5 | "testing"
6 |
7 | "github.com/google/uuid"
8 | "github.com/mr-tron/base58"
9 | "github.com/stretchr/testify/assert"
10 | "github.com/stretchr/testify/require"
11 | "google.golang.org/protobuf/proto"
12 |
13 | commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
14 | messagingpb "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1"
15 |
16 | "github.com/stickychin/code-server/pkg/code/data/messaging"
17 | )
18 |
19 | func TestModelConversion(t *testing.T) {
20 | pub, _, err := ed25519.GenerateKey(nil)
21 | require.NoError(t, err)
22 | requestor, _, err := ed25519.GenerateKey(nil)
23 | require.NoError(t, err)
24 |
25 | account := &commonpb.SolanaAccountId{Value: pub}
26 | messageID := uuid.New()
27 | idBytes, _ := messageID.MarshalBinary()
28 | message := &messagingpb.Message{
29 | Id: &messagingpb.MessageId{
30 | Value: idBytes,
31 | },
32 | Kind: &messagingpb.Message_RequestToGrabBill{
33 | RequestToGrabBill: &messagingpb.RequestToGrabBill{
34 | RequestorAccount: &commonpb.SolanaAccountId{
35 | Value: requestor,
36 | },
37 | },
38 | },
39 | }
40 | messageBytes, err := proto.Marshal(message)
41 | require.NoError(t, err)
42 |
43 | record := &messaging.Record{
44 | Account: base58.Encode(account.Value),
45 | MessageID: messageID,
46 | Message: messageBytes,
47 | }
48 |
49 | model, err := toModel(record)
50 | require.NoError(t, err)
51 | assert.Equal(t, model.Account, base58.Encode(account.Value))
52 | assert.Equal(t, model.MessageID, messageID.String())
53 | assert.Equal(t, model.Message, messageBytes)
54 |
55 | actual, err := fromModel(model)
56 | require.NoError(t, err)
57 | assert.Equal(t, actual, record)
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/code/data/messaging/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/google/uuid"
8 | "github.com/jmoiron/sqlx"
9 |
10 | "github.com/stickychin/code-server/pkg/code/data/messaging"
11 | )
12 |
13 | // todo: This doesn't support TTL expiries, which is fine for now. We can
14 | // manually delete old entries while in an invite-only testing phase.
15 | type store struct {
16 | db *sqlx.DB
17 | }
18 |
19 | // New returns a postgres backed messaging.Store.
20 | func New(db *sql.DB) messaging.Store {
21 | return &store{
22 | db: sqlx.NewDb(db, "pgx"),
23 | }
24 | }
25 |
26 | // Insert implements messaging.Store.Insert.
27 | func (s *store) Insert(ctx context.Context, record *messaging.Record) error {
28 | model, err := toModel(record)
29 | if err != nil {
30 | return err
31 | }
32 |
33 | return model.dbSave(ctx, s.db)
34 | }
35 |
36 | // Delete implements messaging.Store.Delete.
37 | func (s *store) Delete(ctx context.Context, account string, messageID uuid.UUID) error {
38 | return dbDelete(ctx, s.db, account, messageID.String())
39 | }
40 |
41 | // Get implements messaging.Store.Get.
42 | func (s *store) Get(ctx context.Context, account string) ([]*messaging.Record, error) {
43 | models, err := dbGetAllForAccount(ctx, s.db, account)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | records := make([]*messaging.Record, len(models))
49 | for i, model := range models {
50 | record, err := fromModel(model)
51 | if err != nil {
52 | // todo(safety): this is the equivalent QoS brick case, although should be less problematic.
53 | // we could have a valve to ignore, and also to delete
54 | return nil, err
55 | }
56 | records[i] = record
57 | }
58 |
59 | return records, nil
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/code/data/messaging/store.go:
--------------------------------------------------------------------------------
1 | package messaging
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/google/uuid"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | var (
11 | ErrDuplicateMessageID = errors.New("duplicate message id")
12 | )
13 |
14 | type Record struct {
15 | Account string
16 | MessageID uuid.UUID
17 | Message []byte
18 | }
19 |
20 | // Store stores messages.
21 | type Store interface {
22 | // Insert inserts a message into a specified bin.
23 | //
24 | // ErrDuplicateMessageID is returned if the message's ID already exists in the bin.
25 | Insert(ctx context.Context, record *Record) error
26 |
27 | // Delete deletes a message in the specified bin.
28 | //
29 | // Delete is idempotent.
30 | Delete(ctx context.Context, account string, messageID uuid.UUID) error
31 |
32 | // Get returns the messages in a bin.
33 | Get(ctx context.Context, account string) ([]*Record, error)
34 | }
35 |
36 | func (r *Record) Validate() error {
37 | if len(r.Account) == 0 {
38 | return errors.New("account is required")
39 | }
40 |
41 | var defaultUUID uuid.UUID
42 | if r.MessageID == defaultUUID {
43 | return errors.New("message id is required")
44 | }
45 |
46 | if len(r.Message) == 0 {
47 | return errors.New("message is required")
48 | }
49 |
50 | return nil
51 | }
52 |
53 | func (r *Record) Clone() Record {
54 | copied := Record{
55 | Account: r.Account,
56 | Message: make([]byte, len(r.Message)),
57 | }
58 |
59 | copy(copied.MessageID[:], r.MessageID[:])
60 | copy(copied.Message, r.Message)
61 |
62 | return copied
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/code/data/nonce/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/nonce/tests"
7 | )
8 |
9 | func TestNonceMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/nonce/store.go:
--------------------------------------------------------------------------------
1 | package nonce
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/database/query"
7 | )
8 |
9 | type Store interface {
10 | // Count returns the total count of nonce accounts within an environment instance
11 | Count(ctx context.Context, env Environment, instance string) (uint64, error)
12 |
13 | // CountByState returns the total count of nonce accounts in the provided state within
14 | // an environment instance
15 | CountByState(ctx context.Context, env Environment, instance string, state State) (uint64, error)
16 |
17 | // CountByStateAndPurpose returns the total count of nonce accounts in the provided
18 | // state and use case within an environment instance
19 | CountByStateAndPurpose(ctx context.Context, env Environment, instance string, state State, purpose Purpose) (uint64, error)
20 |
21 | // Save creates or updates nonce metadata in the store.
22 | Save(ctx context.Context, record *Record) error
23 |
24 | // Get finds the nonce record for a given address.
25 | //
26 | // Returns ErrNotFound if no record is found.
27 | Get(ctx context.Context, address string) (*Record, error)
28 |
29 | // GetAllByState returns nonce records in the store for a given confirmation state
30 | // within an environment intance.
31 | //
32 | // Returns ErrNotFound if no records are found.
33 | GetAllByState(ctx context.Context, env Environment, instance string, state State, cursor query.Cursor, limit uint64, direction query.Ordering) ([]*Record, error)
34 |
35 | // GetRandomAvailableByPurpose gets a random available nonce for a purpose within
36 | // an environment instance.
37 | //
38 | // Returns ErrNotFound if no records are found.
39 | GetRandomAvailableByPurpose(ctx context.Context, env Environment, instance string, purpose Purpose) (*Record, error)
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/code/data/onramp/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/onramp/tests"
7 | )
8 |
9 | func TestOnrampMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/onramp/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/google/uuid"
8 | "github.com/jmoiron/sqlx"
9 |
10 | "github.com/stickychin/code-server/pkg/code/data/onramp"
11 | )
12 |
13 | type store struct {
14 | db *sqlx.DB
15 | }
16 |
17 | // New returns a new postgres-backed onramp.Store
18 | func New(db *sql.DB) onramp.Store {
19 | return &store{
20 | db: sqlx.NewDb(db, "pgx"),
21 | }
22 | }
23 |
24 | // Put implements onramp.Store.Put
25 | func (s *store) Put(ctx context.Context, record *onramp.Record) error {
26 | model, err := toModel(record)
27 | if err != nil {
28 | return err
29 | }
30 |
31 | if err := model.dbPut(ctx, s.db); err != nil {
32 | return err
33 | }
34 |
35 | res := fromModel(model)
36 | res.CopyTo(record)
37 |
38 | return nil
39 | }
40 |
41 | // Get implements onramp.Store.Get
42 | func (s *store) Get(ctx context.Context, nonce uuid.UUID) (*onramp.Record, error) {
43 | model, err := dbGet(ctx, s.db, nonce)
44 | if err != nil {
45 | return nil, err
46 | }
47 | return fromModel(model), nil
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/code/data/onramp/purchase.go:
--------------------------------------------------------------------------------
1 | package onramp
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "time"
7 |
8 | "github.com/google/uuid"
9 | )
10 |
11 | // todo: We can track provider, fulfillment state, time to fulfillment, etc.
12 | type Record struct {
13 | Id uint64
14 |
15 | Owner string
16 | Platform int // todo: can't use client.DeviceType due to import cycle
17 | Currency string
18 | Amount float64
19 | Nonce uuid.UUID
20 |
21 | CreatedAt time.Time
22 | }
23 |
24 | func (r *Record) Validate() error {
25 | if len(r.Owner) == 0 {
26 | return errors.New("owner is required")
27 | }
28 |
29 | if len(r.Currency) == 0 {
30 | return errors.New("currency is required")
31 | }
32 |
33 | if r.Amount <= 0 {
34 | return errors.New("amount must be positive")
35 | }
36 |
37 | if bytes.Equal(r.Nonce[:], uuid.Nil[:]) {
38 | return errors.New("nonce is required")
39 | }
40 |
41 | return nil
42 | }
43 |
44 | func (r *Record) Clone() Record {
45 | return Record{
46 | Id: r.Id,
47 |
48 | Owner: r.Owner,
49 | Platform: r.Platform,
50 | Currency: r.Currency,
51 | Amount: r.Amount,
52 | Nonce: r.Nonce,
53 |
54 | CreatedAt: r.CreatedAt,
55 | }
56 | }
57 |
58 | func (r *Record) CopyTo(dst *Record) {
59 | dst.Id = r.Id
60 |
61 | dst.Owner = r.Owner
62 | dst.Platform = r.Platform
63 | dst.Currency = r.Currency
64 | dst.Amount = r.Amount
65 | dst.Nonce = r.Nonce
66 |
67 | dst.CreatedAt = r.CreatedAt
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/code/data/onramp/store.go:
--------------------------------------------------------------------------------
1 | package onramp
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/google/uuid"
8 | )
9 |
10 | var (
11 | // ErrPurchaseAlreadyExists indicates an onramp purchase record already exists
12 | ErrPurchaseAlreadyExists = errors.New("onramp purchase record already exists")
13 |
14 | // ErrPurchaseNotFound indicates an onramp purchase record doesn't exists
15 | ErrPurchaseNotFound = errors.New("onramp purchase record not found")
16 | )
17 |
18 | type Store interface {
19 | // Put creates a new onramp purchase record
20 | Put(ctx context.Context, record *Record) error
21 |
22 | // Get gets an onramp purchase record by nonce
23 | Get(ctx context.Context, nonce uuid.UUID) (*Record, error)
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/code/data/paymentrequest/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/paymentrequest/tests"
7 | )
8 |
9 | func TestPaymentRequestMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/paymentrequest/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/paymentrequest"
10 | )
11 |
12 | type store struct {
13 | db *sqlx.DB
14 | }
15 |
16 | func New(db *sql.DB) paymentrequest.Store {
17 | return &store{
18 | db: sqlx.NewDb(db, "pgx"),
19 | }
20 | }
21 |
22 | // Put implements paymentrequest.Store.Put
23 | func (s *store) Put(ctx context.Context, record *paymentrequest.Record) error {
24 | m, err := toRequestModel(record)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | err = m.dbPut(ctx, s.db)
30 | if err != nil {
31 | return err
32 | }
33 |
34 | res := fromRequestModel(m)
35 | res.CopyTo(record)
36 |
37 | return nil
38 | }
39 |
40 | // Get implements paymentrequest.Store.Get
41 | func (s *store) Get(ctx context.Context, intentId string) (*paymentrequest.Record, error) {
42 | m, err := dbGet(ctx, s.db, intentId)
43 | if err != nil {
44 | return nil, err
45 | }
46 | return fromRequestModel(m), nil
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/code/data/paymentrequest/store.go:
--------------------------------------------------------------------------------
1 | package paymentrequest
2 |
3 | // todo: Refactor this package to a generic "request" model, similar to intent.
4 | // This package started with the payments use case, but is quickly evolving
5 | // beyond that. Naming is skewed towards payments here until refactor.
6 |
7 | import (
8 | "context"
9 | "errors"
10 | )
11 |
12 | var (
13 | ErrPaymentRequestAlreadyExists = errors.New("payment request record already exists")
14 | ErrPaymentRequestNotFound = errors.New("no payment request records could be found")
15 | ErrInvalidPaymentRequest = errors.New("payment request is invalid")
16 | )
17 |
18 | type Store interface {
19 | // Put creates a new payment request record
20 | Put(ctx context.Context, record *Record) error
21 |
22 | // Get gets a paymen request record by its intent ID
23 | Get(ctx context.Context, intentId string) (*Record, error)
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/code/data/rendezvous/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/rendezvous/tests"
7 | )
8 |
9 | func TestRendezvousMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 |
15 | tests.RunTests(t, testStore, teardown)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/code/data/rendezvous/postgres/store.go:
--------------------------------------------------------------------------------
1 | package postgres
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 |
7 | "github.com/jmoiron/sqlx"
8 |
9 | "github.com/stickychin/code-server/pkg/code/data/rendezvous"
10 | )
11 |
12 | type store struct {
13 | db *sqlx.DB
14 | }
15 |
16 | // New returns a new postgres-backed rendezvous.Store
17 | func New(db *sql.DB) rendezvous.Store {
18 | return &store{
19 | db: sqlx.NewDb(db, "pgx"),
20 | }
21 | }
22 |
23 | // Save implements rendezvous.Store.Save
24 | func (s *store) Save(ctx context.Context, record *rendezvous.Record) error {
25 | obj, err := toModel(record)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | err = obj.dbSave(ctx, s.db)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | res := fromModel(obj)
36 | res.CopyTo(record)
37 |
38 | return nil
39 | }
40 |
41 | // Get implements rendezvous.Store.Get
42 | func (s *store) Get(ctx context.Context, key string) (*rendezvous.Record, error) {
43 | model, err := dbGetByKey(ctx, s.db, key)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | return fromModel(model), nil
49 | }
50 |
51 | // Delete implements rendezvous.Store.Delete
52 | func (s *store) Delete(ctx context.Context, key string) error {
53 | return dbDelete(ctx, s.db, key)
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/code/data/rendezvous/store.go:
--------------------------------------------------------------------------------
1 | package rendezvous
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "time"
7 | )
8 |
9 | var (
10 | ErrNotFound = errors.New("rendezvous record not found")
11 | )
12 |
13 | type Record struct {
14 | Id uint64
15 | Key string
16 | Location string
17 | CreatedAt time.Time
18 | LastUpdatedAt time.Time
19 | }
20 |
21 | type Store interface {
22 | Save(ctx context.Context, record *Record) error
23 |
24 | Get(ctx context.Context, key string) (*Record, error)
25 |
26 | Delete(ctx context.Context, key string) error
27 | }
28 |
29 | func (r *Record) Validate() error {
30 | if len(r.Key) == 0 {
31 | return errors.New("key is required")
32 | }
33 |
34 | if len(r.Location) == 0 {
35 | return errors.New("location is required")
36 | }
37 |
38 | return nil
39 | }
40 |
41 | func (r *Record) Clone() Record {
42 | return Record{
43 | Id: r.Id,
44 | Key: r.Key,
45 | Location: r.Location,
46 | CreatedAt: r.CreatedAt,
47 | LastUpdatedAt: r.LastUpdatedAt,
48 | }
49 | }
50 |
51 | func (r *Record) CopyTo(dst *Record) {
52 | dst.Id = r.Id
53 | dst.Key = r.Key
54 | dst.Location = r.Location
55 | dst.CreatedAt = r.CreatedAt
56 | dst.LastUpdatedAt = r.LastUpdatedAt
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/code/data/timelock/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/timelock/tests"
7 | )
8 |
9 | func TestTimelockMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/timelock/store.go:
--------------------------------------------------------------------------------
1 | package timelock
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/database/query"
7 | timelock_token "github.com/stickychin/code-server/pkg/solana/timelock/v1"
8 | )
9 |
10 | type Store interface {
11 | // Save saves a timelock account's state
12 | Save(ctx context.Context, record *Record) error
13 |
14 | // GetByAddress gets a timelock account's state by the state address
15 | GetByAddress(ctx context.Context, address string) (*Record, error)
16 |
17 | // GetByVault gets a timelock account's state by the vault address it's locking
18 | GetByVault(ctx context.Context, vault string) (*Record, error)
19 |
20 | // GetByDepositPda gets a timelock account's state by the deposit PDA address
21 | GetByDepositPda(ctx context.Context, depositPda string) (*Record, error)
22 |
23 | // GetByVaultBatch is like GetByVault, but for multiple accounts. If any one account
24 | // is missing, ErrTimelockNotFound is returned.
25 | GetByVaultBatch(ctx context.Context, vaults ...string) (map[string]*Record, error)
26 |
27 | // GetAllByState gets all timelock accounts in the provided state
28 | GetAllByState(ctx context.Context, state timelock_token.TimelockState, cursor query.Cursor, limit uint64, direction query.Ordering) ([]*Record, error)
29 |
30 | // GetCountByState gets the count of records in the provided state
31 | GetCountByState(ctx context.Context, state timelock_token.TimelockState) (uint64, error)
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/code/data/transaction/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/transaction/tests"
7 | )
8 |
9 | func TestTransactionMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/transaction/store.go:
--------------------------------------------------------------------------------
1 | package transaction
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/database/query"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | var (
11 | ErrNotFound = errors.New("no records could be found")
12 | ErrExists = errors.New("the transaction already exists")
13 | )
14 |
15 | type Store interface {
16 | // Get returns a transaction record for the given signature.
17 | //
18 | // ErrNotFound is returned if no record is found.
19 | Get(ctx context.Context, txId string) (*Record, error)
20 |
21 | // Put saves transaction data to the store.
22 | //
23 | // ErrExists is returned if a transaction with the same signature already exists.
24 | Put(ctx context.Context, transaction *Record) error
25 |
26 | // GetAllByAddress returns a list of records that match a given address.
27 | //
28 | // ErrNotFound is returned if no records are found.
29 | GetAllByAddress(ctx context.Context, address string, cursor uint64, limit uint, ordering query.Ordering) ([]*Record, error)
30 |
31 | // GetLatestByState returns the latest record for a given state.
32 | //
33 | // ErrNotFound is returned if no records are found.
34 | GetLatestByState(ctx context.Context, address string, state Confirmation) (*Record, error)
35 |
36 | // GetFirstPending returns the latest record for a given state.
37 | //
38 | // ErrNotFound is returned if no records are found.
39 | GetFirstPending(ctx context.Context, address string) (*Record, error)
40 |
41 | // GetSignaturesByState returns a list of signatures that match given a confirmation state.
42 | //
43 | // ErrNotFound is returned if no records are found.
44 | GetSignaturesByState(ctx context.Context, filter Confirmation, limit uint, ordering query.Ordering) ([]string, error)
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/code/data/vault/key_test.go:
--------------------------------------------------------------------------------
1 | package vault_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/vault"
7 | "github.com/mr-tron/base58/base58"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestCreateKey(t *testing.T) {
13 |
14 | for i := 0; i < 100; i++ {
15 | actual, err := vault.CreateKey()
16 | require.NoError(t, err)
17 | assert.Equal(t, vault.StateAvailable, actual.State)
18 |
19 | _, err = base58.Decode(actual.PublicKey)
20 | require.NoError(t, err)
21 |
22 | _, err = base58.Decode(actual.PrivateKey)
23 | require.NoError(t, err)
24 | }
25 |
26 | }
27 |
28 | func TestGrindKey(t *testing.T) {
29 | for i := 0; i < 5; i++ {
30 | actual, err := vault.GrindKey("x")
31 | require.NoError(t, err)
32 | assert.Equal(t, vault.StateAvailable, actual.State)
33 | assert.EqualValues(t, 'x', actual.PublicKey[0])
34 |
35 | _, err = base58.Decode(actual.PublicKey)
36 | require.NoError(t, err)
37 |
38 | _, err = base58.Decode(actual.PrivateKey)
39 | require.NoError(t, err)
40 | }
41 |
42 | for i := 0; i < 5; i++ {
43 | actual, err := vault.GrindKey("y")
44 | require.NoError(t, err)
45 | assert.Equal(t, vault.StateAvailable, actual.State)
46 | assert.EqualValues(t, 'y', actual.PublicKey[0])
47 |
48 | _, err = base58.Decode(actual.PublicKey)
49 | require.NoError(t, err)
50 |
51 | _, err = base58.Decode(actual.PrivateKey)
52 | require.NoError(t, err)
53 | }
54 |
55 | for i := 0; i < 5; i++ {
56 | actual, err := vault.GrindKey("z")
57 | require.NoError(t, err)
58 | assert.Equal(t, vault.StateAvailable, actual.State)
59 | assert.EqualValues(t, 'z', actual.PublicKey[0])
60 |
61 | _, err = base58.Decode(actual.PublicKey)
62 | require.NoError(t, err)
63 |
64 | _, err = base58.Decode(actual.PrivateKey)
65 | require.NoError(t, err)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/code/data/vault/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/vault/tests"
7 | )
8 |
9 | func TestFulfillmentMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 | tests.RunTests(t, testStore, teardown)
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/code/data/vault/secret.go:
--------------------------------------------------------------------------------
1 | package vault
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | const (
8 | vaultSecretKeyEnv = "VAULT_SECRET_KEY"
9 | defaultVaultSecret = "DpmmM8ruhZ5C26USxxEMBfQGAJPzHen6NxNrfKkyaBTB" // for local testing
10 | )
11 |
12 | func GetSecret() string {
13 | secret := os.Getenv(vaultSecretKeyEnv)
14 | if len(secret) == 0 {
15 | secret = defaultVaultSecret
16 | }
17 |
18 | return secret
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/code/data/vault/store.go:
--------------------------------------------------------------------------------
1 | package vault
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/stickychin/code-server/pkg/database/query"
7 | )
8 |
9 | // todo: migrate to AWS KMS or similar
10 |
11 | type Store interface {
12 | // Count returns the total count of keys.
13 | Count(ctx context.Context) (uint64, error)
14 |
15 | // CountByState returns the total count of keys by state
16 | CountByState(ctx context.Context, state State) (uint64, error)
17 |
18 | // Save creates or updates the record in the store.
19 | Save(ctx context.Context, record *Record) error
20 |
21 | // Get finds the record for a given public key.
22 | Get(ctx context.Context, pubkey string) (*Record, error)
23 |
24 | // GetAllByState returns all records for a given state.
25 | //
26 | // Returns ErrKeyNotFound if no records are found.
27 | GetAllByState(ctx context.Context, state State, cursor query.Cursor, limit uint64, direction query.Ordering) ([]*Record, error)
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/code/data/webhook/memory/store_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stickychin/code-server/pkg/code/data/webhook/tests"
7 | )
8 |
9 | func TestWebhookMemoryStore(t *testing.T) {
10 | testStore := New()
11 | teardown := func() {
12 | testStore.(*store).reset()
13 | }
14 |
15 | tests.RunTests(t, testStore, teardown)
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/code/data/webhook/store.go:
--------------------------------------------------------------------------------
1 | package webhook
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/pkg/errors"
7 | )
8 |
9 | var (
10 | ErrNotFound = errors.New("webhook record not found")
11 | ErrAlreadyExists = errors.New("webhook record already exists")
12 | )
13 |
14 | type Store interface {
15 | // Put creates a webhook record
16 | //
17 | // Returns ErrAlreadyExists if a record already exists.
18 | Put(ctx context.Context, record *Record) error
19 |
20 | // Update updates a webhook record
21 | //
22 | // Returns ErrNotFound if no record exists.
23 | Update(ctx context.Context, record *Record) error
24 |
25 | // Get finds the webhook record for a given webhook ID
26 | //
27 | // Returns ErrNotFound if no record is found.
28 | Get(ctx context.Context, webhookId string) (*Record, error)
29 |
30 | // CountByState counts all webhook records in a provided state
31 | CountByState(ctx context.Context, state State) (uint64, error)
32 |
33 | // GetAllPendingReadyToSend gets all webhook records in the pending state
34 | // that have an attempt scheduled to be sent.
35 | //
36 | // Returns ErrNotFound if no record is found.
37 | //
38 | // Note: No traditional pagination since it's expected the next attempt
39 | // timestamp is updated or the state transitions to a terminal value.
40 | GetAllPendingReadyToSend(ctx context.Context, limit uint64) ([]*Record, error)
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/code/exchangerate/time.go:
--------------------------------------------------------------------------------
1 | package exchangerate
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // todo: add tests, but generally well tested in server tests since that's where most of this originated
8 | // todo: does this belong in an exchange-specific package?
9 |
10 | // GetLatestExchangeRateTime gets the latest time for fetching an exchange rate.
11 | // By synchronizing on a time, we can eliminate the amount of perceived volatility
12 | // over short time spans.
13 | func GetLatestExchangeRateTime() time.Time {
14 | // Standardize to concrete 15 minute intervals to reduce perceived volatility.
15 | // Notably, don't fall exactly on the 15 minute interval, so we remove 1 second.
16 | // The way our postgres DB query is setup, the start of UTC day is unlikely to
17 | // generate results.
18 | secondsIn15Minutes := int64(15 * time.Minute / time.Second)
19 | queryTimeUnix := time.Now().Unix()
20 | queryTimeUnix = queryTimeUnix - (queryTimeUnix % secondsIn15Minutes) - 1
21 | return time.Unix(queryTimeUnix, 0)
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/code/server/messaging/client.go:
--------------------------------------------------------------------------------
1 | package messaging
2 |
3 | import (
4 | "google.golang.org/grpc"
5 | "google.golang.org/grpc/credentials/insecure"
6 |
7 | messagingpb "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1"
8 | "github.com/stickychin/code-server/pkg/grpc/headers"
9 | "github.com/stickychin/code-server/pkg/grpc/protobuf/validation"
10 | )
11 |
12 | // todo: we can cache and reuse clients
13 | func getInternalMessagingClient(target string) (messagingpb.MessagingClient, func() error, error) {
14 | conn, err := grpc.Dial(
15 | target,
16 |
17 | grpc.WithTransportCredentials(insecure.NewCredentials()),
18 |
19 | grpc.WithUnaryInterceptor(validation.UnaryClientInterceptor()),
20 | grpc.WithUnaryInterceptor(headers.UnaryClientInterceptor()),
21 |
22 | grpc.WithStreamInterceptor(validation.StreamClientInterceptor()),
23 | grpc.WithStreamInterceptor(headers.StreamClientInterceptor()),
24 | )
25 | if err != nil {
26 | return nil, nil, err
27 | }
28 |
29 | return messagingpb.NewMessagingClient(conn), conn.Close, nil
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/code/server/messaging/config.go:
--------------------------------------------------------------------------------
1 | package messaging
2 |
3 | import (
4 | "github.com/stickychin/code-server/pkg/config"
5 | "github.com/stickychin/code-server/pkg/config/env"
6 | "github.com/stickychin/code-server/pkg/config/memory"
7 | "github.com/stickychin/code-server/pkg/config/wrapper"
8 | )
9 |
10 | const (
11 | envConfigPrefix = "MESSAGING_SERVICE_"
12 |
13 | DisableBlockchainChecksConfigEnvName = envConfigPrefix + "DISABLE_BLOCKCHAIN_CHECKS"
14 | defaultDisableBlockchainChecks = false
15 |
16 | MaxFeeBasisPointsConfigEnvName = envConfigPrefix + "MAX_FEE_BASIS_POINTS"
17 | defaultMaxFeeBasisPoints = 5000
18 | )
19 |
20 | type conf struct {
21 | disableBlockchainChecks config.Bool
22 | maxFeeBasisPoints config.Uint64
23 | }
24 |
25 | // ConfigProvider defines how config values are pulled
26 | type ConfigProvider func() *conf
27 |
28 | // WithEnvConfigs returns configuration pulled from environment variables
29 | func WithEnvConfigs() ConfigProvider {
30 | return func() *conf {
31 | return &conf{
32 | disableBlockchainChecks: env.NewBoolConfig(DisableBlockchainChecksConfigEnvName, defaultDisableBlockchainChecks),
33 | maxFeeBasisPoints: env.NewUint64Config(MaxFeeBasisPointsConfigEnvName, defaultMaxFeeBasisPoints),
34 | }
35 | }
36 | }
37 |
38 | type testOverrides struct {
39 | }
40 |
41 | func withManualTestOverrides(overrides *testOverrides) ConfigProvider {
42 | return func() *conf {
43 | return &conf{
44 | disableBlockchainChecks: wrapper.NewBoolConfig(memory.NewConfig(true), true),
45 | maxFeeBasisPoints: wrapper.NewUint64Config(memory.NewConfig(defaultMaxFeeBasisPoints), defaultMaxFeeBasisPoints),
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/code/server/messaging/error.go:
--------------------------------------------------------------------------------
1 | package messaging
2 |
3 | import "fmt"
4 |
5 | type MessageValidationError struct {
6 | message string
7 | }
8 |
9 | func newMessageValidationError(message string) MessageValidationError {
10 | return MessageValidationError{
11 | message: message,
12 | }
13 | }
14 |
15 | func newMessageValidationErrorf(format string, args ...any) MessageValidationError {
16 | return newMessageValidationError(fmt.Sprintf(format, args...))
17 | }
18 |
19 | func (e MessageValidationError) Error() string {
20 | return e.message
21 | }
22 |
23 | type MessageAuthenticationError struct {
24 | message string
25 | }
26 |
27 | func newMessageAuthenticationError(message string) MessageAuthenticationError {
28 | return MessageAuthenticationError{
29 | message: message,
30 | }
31 | }
32 |
33 | func newMessageAuthenticationErrorf(format string, args ...any) MessageAuthenticationError {
34 | return newMessageAuthenticationError(fmt.Sprintf(format, args...))
35 | }
36 |
37 | func (e MessageAuthenticationError) Error() string {
38 | return e.message
39 | }
40 |
41 | type MessageAuthorizationError struct {
42 | message string
43 | }
44 |
45 | func newMessageAuthorizationError(message string) MessageAuthorizationError {
46 | return MessageAuthorizationError{
47 | message: message,
48 | }
49 | }
50 |
51 | func newMessageAuthorizationErrorf(format string, args ...any) MessageAuthorizationError {
52 | return newMessageAuthorizationError(fmt.Sprintf(format, args...))
53 | }
54 |
55 | func (e MessageAuthorizationError) Error() string {
56 | return e.message
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/code/server/messaging/stream.go:
--------------------------------------------------------------------------------
1 | package messaging
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "github.com/pkg/errors"
8 | "google.golang.org/protobuf/proto"
9 |
10 | messagingpb "github.com/code-payments/code-protobuf-api/generated/go/messaging/v1"
11 | )
12 |
13 | type messageStream struct {
14 | sync.Mutex
15 |
16 | closed bool
17 | streamCh chan *messagingpb.Message
18 | }
19 |
20 | func newMessageStream(bufferSize int) *messageStream {
21 | return &messageStream{
22 | streamCh: make(chan *messagingpb.Message, bufferSize),
23 | }
24 | }
25 |
26 | func (s *messageStream) notify(msg *messagingpb.Message, timeout time.Duration) error {
27 | m := proto.Clone(msg).(*messagingpb.Message)
28 |
29 | s.Lock()
30 |
31 | if s.closed {
32 | s.Unlock()
33 | return errors.New("cannot notify closed stream")
34 | }
35 |
36 | select {
37 | case s.streamCh <- m:
38 | case <-time.After(timeout):
39 | s.Unlock()
40 | s.close()
41 | return errors.New("timed out sending message to streamCh")
42 | }
43 |
44 | s.Unlock()
45 | return nil
46 | }
47 |
48 | func (s *messageStream) close() {
49 | s.Lock()
50 | defer s.Unlock()
51 |
52 | if s.closed {
53 | return
54 | }
55 |
56 | s.closed = true
57 | close(s.streamCh)
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/code/server/transaction/limits_test.go:
--------------------------------------------------------------------------------
1 | package transaction_v2
2 |
3 | // todo: implement me
4 |
--------------------------------------------------------------------------------
/pkg/code/transaction/instruction.go:
--------------------------------------------------------------------------------
1 | package transaction
2 |
3 | import (
4 | "github.com/stickychin/code-server/pkg/code/common"
5 | "github.com/stickychin/code-server/pkg/solana"
6 | "github.com/stickychin/code-server/pkg/solana/system"
7 | )
8 |
9 | // todo: start moving instruction construction code here
10 |
11 | func makeAdvanceNonceInstruction(nonce *common.Account) (solana.Instruction, error) {
12 | return system.AdvanceNonce(
13 | nonce.PublicKey().ToBytes(),
14 | common.GetSubsidizer().PublicKey().ToBytes(),
15 | ), nil
16 | }
17 |
--------------------------------------------------------------------------------
/pkg/config/env/config_test.go:
--------------------------------------------------------------------------------
1 | package env
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 |
10 | "github.com/stickychin/code-server/pkg/config"
11 | )
12 |
13 | func TestConfigDoesntExist(t *testing.T) {
14 | const env = "ENV_CONFIG_TEST_VAR"
15 | os.Setenv(env, "default")
16 |
17 | v, err := NewConfig(env).Get(context.Background())
18 | assert.Equal(t, []byte("default"), v)
19 | assert.Nil(t, err)
20 |
21 | os.Unsetenv(env)
22 |
23 | v, err = NewConfig(env).Get(context.Background())
24 | assert.Nil(t, v)
25 | assert.Equal(t, config.ErrNoValue, err)
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/config/memory/config_test.go:
--------------------------------------------------------------------------------
1 | package memory
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 |
10 | "github.com/stickychin/code-server/pkg/config"
11 | )
12 |
13 | func TestHappyPath(t *testing.T) {
14 | c := NewConfig(nil)
15 | _, err := c.Get(context.Background())
16 | assert.Equal(t, config.ErrNoValue, err)
17 |
18 | expected := "value"
19 | c.SetValue(expected)
20 | val, err := c.Get(context.Background())
21 | require.NoError(t, err)
22 | assert.Equal(t, expected, val)
23 |
24 | c.ClearValue()
25 | _, err = c.Get(context.Background())
26 | assert.Equal(t, config.ErrNoValue, err)
27 |
28 | c.InduceErrors()
29 | _, err = c.Get(context.Background())
30 | assert.Equal(t, errDeveloperInduced, err)
31 |
32 | c.StopInducingErrors()
33 | _, err = c.Get(context.Background())
34 | assert.Equal(t, config.ErrNoValue, err)
35 |
36 | c.Shutdown()
37 | _, err = c.Get(context.Background())
38 | assert.Equal(t, config.ErrShutdown, err)
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/currency/client.go:
--------------------------------------------------------------------------------
1 | package currency
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "time"
7 | )
8 |
9 | var (
10 | ErrInvalidBase = errors.New("invalid base currency")
11 | )
12 |
13 | type ExchangeData struct {
14 | Base string
15 | Rates map[string]float64
16 | Timestamp time.Time
17 | }
18 |
19 | type Client interface {
20 | // GetCurrentRates gets the current set of exchange rates against a base
21 | // currency.
22 | GetCurrentRates(ctx context.Context, base string) (*ExchangeData, error)
23 |
24 | // GetHistoricalRates gets the historical set of exchange rates against a
25 | // base currency. Granularity of time intervals may be significantly reduced.
26 | // A databse of cached rates from periodic calls to GetCurrentRates is the
27 | // recommended workaround.
28 | GetHistoricalRates(ctx context.Context, base string, timestamp time.Time) (*ExchangeData, error)
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/database/postgres/errors.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "database/sql"
5 |
6 | "github.com/jackc/pgconn"
7 | "github.com/jackc/pgerrcode"
8 | "github.com/pkg/errors"
9 | )
10 |
11 | func CheckNoRows(inErr, outErr error) error {
12 | if inErr == sql.ErrNoRows {
13 | return outErr
14 | }
15 | return inErr
16 | }
17 |
18 | func CheckUniqueViolation(inErr, outErr error) error {
19 | if inErr != nil {
20 | var pgErr *pgconn.PgError
21 | if errors.As(inErr, &pgErr) {
22 | if pgErr.Code == pgerrcode.UniqueViolation {
23 | return outErr
24 | }
25 | }
26 | }
27 | return inErr
28 | }
29 |
30 | func IsUniqueViolation(err error) bool {
31 | if err == nil {
32 | return false
33 | }
34 |
35 | var pgErr *pgconn.PgError
36 | if errors.As(err, &pgErr) {
37 | if pgErr.Code == pgerrcode.UniqueViolation {
38 | return true
39 | }
40 | }
41 |
42 | return false
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/database/query/cursor.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "encoding/binary"
5 |
6 | "github.com/mr-tron/base58"
7 | )
8 |
9 | type Cursor []byte
10 |
11 | var (
12 | EmptyCursor Cursor = Cursor([]byte{})
13 | )
14 |
15 | func ToCursor(val uint64) Cursor {
16 | b := make([]byte, 8)
17 | binary.BigEndian.PutUint64(b, val)
18 | return b
19 | }
20 |
21 | func FromCursor(val []byte) uint64 {
22 | return binary.BigEndian.Uint64(val)
23 | }
24 |
25 | func (c Cursor) ToUint64() uint64 {
26 | return binary.BigEndian.Uint64(c)
27 | }
28 |
29 | func (c Cursor) ToBase58() string {
30 | return base58.Encode(c)
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/database/query/filter.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | type Filter struct {
4 | Value uint64
5 | Valid bool
6 | }
7 |
8 | func NewFilter(value uint64) Filter {
9 | return Filter{
10 | Value: value,
11 | Valid: true,
12 | }
13 | }
14 |
15 | func (f *Filter) IsValid() bool {
16 | return f.Valid
17 | }
18 |
19 | func (f *Filter) Set(value uint64) {
20 | f.Value = value
21 | f.Valid = true
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/database/query/interval.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | )
6 |
7 | type Interval uint8
8 |
9 | const (
10 | IntervalRaw Interval = iota
11 | IntervalHour
12 | IntervalDay
13 | IntervalWeek
14 | IntervalMonth
15 | )
16 |
17 | var AllIntervals = []Interval{
18 | IntervalRaw,
19 | IntervalHour,
20 | IntervalDay,
21 | IntervalWeek,
22 | IntervalMonth,
23 | }
24 |
25 | func ToInterval(val string) (Interval, error) {
26 | switch val {
27 | case "raw":
28 | return IntervalRaw, nil
29 | case "hour":
30 | return IntervalHour, nil
31 | case "day":
32 | return IntervalDay, nil
33 | case "week":
34 | return IntervalWeek, nil
35 | case "month":
36 | return IntervalMonth, nil
37 | default:
38 | return 0, errors.Errorf("unexpected value: %v", val)
39 | }
40 | }
41 |
42 | func FromInterval(val Interval) (string, error) {
43 | switch val {
44 | case IntervalRaw:
45 | return "raw", nil
46 | case IntervalHour:
47 | return "hour", nil
48 | case IntervalDay:
49 | return "day", nil
50 | case IntervalWeek:
51 | return "week", nil
52 | case IntervalMonth:
53 | return "month", nil
54 | default:
55 | return "", errors.Errorf("unexpected value: %v", val)
56 | }
57 | }
58 |
59 | func ToIntervalWithFallback(val string, fallback Interval) Interval {
60 | res, err := ToInterval(val)
61 | if err != nil {
62 | return fallback
63 | }
64 | return res
65 | }
66 |
67 | func FromIntervalWithFallback(val Interval, fallback string) string {
68 | res, err := FromInterval(val)
69 | if err != nil {
70 | return fallback
71 | }
72 | return res
73 | }
74 |
--------------------------------------------------------------------------------
/pkg/database/query/ordering.go:
--------------------------------------------------------------------------------
1 | package query
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | )
6 |
7 | // The ordering of a returned set of records
8 | type Ordering uint
9 |
10 | const (
11 | Ascending Ordering = iota
12 | Descending
13 | )
14 |
15 | func ToOrdering(val string) (Ordering, error) {
16 | switch val {
17 | case "asc":
18 | return Ascending, nil
19 | case "desc":
20 | return Descending, nil
21 | default:
22 | return 0, errors.Errorf("unexpected value: %v", val)
23 | }
24 | }
25 |
26 | func FromOrdering(val Ordering) (string, error) {
27 | switch val {
28 | case Ascending:
29 | return "asc", nil
30 | case Descending:
31 | return "desc", nil
32 | default:
33 | return "", errors.Errorf("unexpected value: %v", val)
34 | }
35 | }
36 |
37 | func ToOrderingWithFallback(val string, fallback Ordering) Ordering {
38 | res, err := ToOrdering(val)
39 | if err != nil {
40 | return fallback
41 | }
42 | return res
43 | }
44 |
45 | func FromOrderingWithFallback(val Ordering, fallback string) string {
46 | res, err := FromOrdering(val)
47 | if err != nil {
48 | return fallback
49 | }
50 | return res
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/etcd/wait.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 |
6 | v3 "go.etcd.io/etcd/client/v3"
7 | )
8 |
9 | func WaitFor(ctx context.Context, client *v3.Client, key string, exists bool) error {
10 | get, err := client.Get(ctx, key)
11 | if err != nil {
12 | return err
13 | }
14 |
15 | if len(get.Kvs) == 0 && !exists {
16 | return nil
17 | } else if len(get.Kvs) > 0 && exists {
18 | return nil
19 | }
20 |
21 | ctx, cancel := context.WithCancel(ctx)
22 | defer cancel()
23 |
24 | watch := client.Watch(ctx, key, v3.WithRev(get.Header.Revision+1))
25 | for w := range watch {
26 | if err = w.Err(); err != nil {
27 | return err
28 | }
29 |
30 | for _, e := range w.Events {
31 | if e.Type == v3.EventTypePut && exists {
32 | return nil
33 | }
34 | if e.Type == v3.EventTypeDelete && !exists {
35 | return nil
36 | }
37 | }
38 | }
39 |
40 | return ctx.Err()
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/grpc/app/loader.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "sync"
7 |
8 | "github.com/pkg/errors"
9 | )
10 |
11 | var (
12 | ctorMu sync.Mutex
13 | ctors = make(map[string]FileLoaderCtor)
14 | )
15 |
16 | // RegisterFileLoaderCtor registers a FileLoader for the specified scheme.
17 | func RegisterFileLoaderCtor(scheme string, ctr FileLoaderCtor) {
18 | ctorMu.Lock()
19 | defer ctorMu.Unlock()
20 |
21 | _, exists := ctors[scheme]
22 | if exists {
23 | panic(fmt.Sprintf("FileLoader already registered for scheme '%s'", scheme))
24 | }
25 |
26 | ctors[scheme] = ctr
27 | }
28 |
29 | // FileLoaderCtor constructs a FileLoader.
30 | type FileLoaderCtor func() (FileLoader, error)
31 |
32 | // FileLoader loads files at a specified URL.
33 | type FileLoader interface {
34 | Load(url *url.URL) ([]byte, error)
35 | }
36 |
37 | // LoadFile loads a file at the specified URL using the corresponding
38 | // registered FileLoader. If no scheme is specified, LocalLoader is used.
39 | func LoadFile(fileURL string) ([]byte, error) {
40 | ctorMu.Lock()
41 | defer ctorMu.Unlock()
42 |
43 | u, err := url.Parse(fileURL)
44 | if err != nil {
45 | return nil, errors.Wrapf(err, "invalid file url %s", fileURL)
46 | }
47 |
48 | ctr, exists := ctors[u.Scheme]
49 | if !exists {
50 | return nil, errors.Errorf("no file loader for %s", u.Scheme)
51 | }
52 |
53 | l, err := ctr()
54 | if err != nil {
55 | return nil, errors.Wrapf(err, "failed get loader for '%s'", fileURL)
56 | }
57 |
58 | return l.Load(u)
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/grpc/app/option.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "google.golang.org/grpc"
5 | )
6 |
7 | // Option configures the environment run by Run().
8 | type Option func(o *opts)
9 |
10 | type opts struct {
11 | unaryServerInterceptors []grpc.UnaryServerInterceptor
12 | streamServerInterceptors []grpc.StreamServerInterceptor
13 | }
14 |
15 | // WithUnaryServerInterceptor configures the app's gRPC server to use the provided interceptor.
16 | //
17 | // Interceptors are evaluated in addition order, and configured interceptors are executed after
18 | // the app's default interceptors.
19 | func WithUnaryServerInterceptor(interceptor grpc.UnaryServerInterceptor) Option {
20 | return func(o *opts) {
21 | o.unaryServerInterceptors = append(o.unaryServerInterceptors, interceptor)
22 | }
23 | }
24 |
25 | // WithStreamServerInterceptor configures the app's gRPC server to use the provided interceptor.
26 | //
27 | // Interceptors are evaluated in addition order, and configured interceptors are executed after
28 | // the app's default interceptors.
29 | func WithStreamServerInterceptor(interceptor grpc.StreamServerInterceptor) Option {
30 | return func(o *opts) {
31 | o.streamServerInterceptors = append(o.streamServerInterceptors, interceptor)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/grpc/client/device.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import "strings"
4 |
5 | type DeviceType uint8
6 |
7 | const (
8 | DeviceTypeUnknown DeviceType = iota
9 | DeviceTypeIOS
10 | DeviceTypeAndroid
11 | )
12 |
13 | func (t DeviceType) String() string {
14 | switch t {
15 | case DeviceTypeUnknown:
16 | return "Unknown"
17 | case DeviceTypeIOS:
18 | return "iOS"
19 | case DeviceTypeAndroid:
20 | return "Android"
21 | }
22 | return "Unknown"
23 | }
24 |
25 | func (t DeviceType) IsMobile() bool {
26 | switch t {
27 | case DeviceTypeIOS, DeviceTypeAndroid:
28 | return true
29 | }
30 | return false
31 | }
32 |
33 | func deviceTypeFromString(value string) DeviceType {
34 | switch strings.ToLower(value) {
35 | case "ios":
36 | return DeviceTypeIOS
37 | case "android":
38 | return DeviceTypeAndroid
39 | }
40 | return DeviceTypeUnknown
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/grpc/client/device_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestDeviceTypeFromString(t *testing.T) {
10 | for _, value := range []string{
11 | "iOS",
12 | "ios",
13 | } {
14 | assert.Equal(t, DeviceTypeIOS, deviceTypeFromString(value))
15 | }
16 |
17 | for _, value := range []string{
18 | "Android",
19 | "android",
20 | } {
21 | assert.Equal(t, DeviceTypeAndroid, deviceTypeFromString(value))
22 | }
23 |
24 | assert.Equal(t, DeviceTypeUnknown, deviceTypeFromString("windows"))
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/grpc/client/ip.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "google.golang.org/grpc/metadata"
8 | )
9 |
10 | const (
11 | clientIPHeader = "x-forwarded-for"
12 | )
13 |
14 | // GetIPAddr gets the client's IP address
15 | func GetIPAddr(ctx context.Context) (string, error) {
16 | mtdt, ok := metadata.FromIncomingContext(ctx)
17 | if !ok {
18 | return "", errors.New("no metdata in context")
19 | }
20 |
21 | ipHeaders := mtdt.Get(clientIPHeader)
22 | if len(ipHeaders) == 0 {
23 | return "", errors.New("x-forwarded-for header not set")
24 | }
25 |
26 | return ipHeaders[0], nil
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/grpc/client/ip_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "google.golang.org/grpc/metadata"
9 | )
10 |
11 | func TestGetIPAddr(t *testing.T) {
12 | tt := map[string]struct {
13 | ctx context.Context
14 | hasIP bool
15 | expectedIP string
16 | }{
17 | "emptyContext": {
18 | ctx: context.Background(),
19 | hasIP: false,
20 | },
21 | "contextWithoutClientIP": {
22 | ctx: metadata.NewIncomingContext(
23 | context.Background(),
24 | metadata.MD{"key": []string{"value"}},
25 | ),
26 | hasIP: false,
27 | },
28 | "contextWithClientIP": {
29 | ctx: metadata.NewIncomingContext(
30 | context.Background(),
31 | metadata.MD{clientIPHeader: []string{"127.0.0.1"}},
32 | ),
33 | hasIP: true,
34 | expectedIP: "127.0.0.1",
35 | },
36 | }
37 |
38 | for name, tc := range tt {
39 | t.Run(name, func(t *testing.T) {
40 | actual, err := GetIPAddr(tc.ctx)
41 | assert.Equal(t, tc.hasIP, err == nil)
42 | assert.Equal(t, tc.expectedIP, actual)
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/grpc/client/logging.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/sirupsen/logrus"
7 | )
8 |
9 | // InjectLoggingMetadata injects client metadata into a logrus log entry
10 | func InjectLoggingMetadata(ctx context.Context, log *logrus.Entry) *logrus.Entry {
11 | userAgent, err := GetUserAgent(ctx)
12 | if err == nil {
13 | log = log.WithField("user_agent", userAgent.String())
14 | }
15 |
16 | ip, err := GetIPAddr(ctx)
17 | if err == nil {
18 | log = log.WithField("client_ip", ip)
19 | }
20 |
21 | return log
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/grpc/client/user_agent.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "regexp"
7 | "strings"
8 |
9 | "github.com/pkg/errors"
10 |
11 | "github.com/stickychin/code-server/pkg/grpc/headers"
12 | )
13 |
14 | const (
15 | UserAgentHeaderName = "user-agent"
16 | )
17 |
18 | var (
19 | userAgentPattern = fmt.Sprintf("Code/(iOS|Android)/%s", versionPattern)
20 | userAgentRegex = regexp.MustCompile(userAgentPattern)
21 | )
22 |
23 | type UserAgent struct {
24 | DeviceType DeviceType
25 | Version Version
26 | }
27 |
28 | func (ua *UserAgent) String() string {
29 | return fmt.Sprintf("Code/%s/%s", ua.DeviceType.String(), ua.Version.String())
30 | }
31 |
32 | // GetUserAgent gets the Code client user agent value from headers in the provided
33 | // context
34 | func GetUserAgent(ctx context.Context) (*UserAgent, error) {
35 | headerValue, err := headers.GetASCIIHeaderByName(ctx, UserAgentHeaderName)
36 | if err != nil {
37 | return nil, errors.Wrap(err, "user agent header not present")
38 | }
39 |
40 | headerValue = strings.TrimSpace(headerValue)
41 |
42 | matches := userAgentRegex.FindAllStringSubmatch(headerValue, -1)
43 | if len(matches) != 1 {
44 | return nil, errors.New("zero or more than one code version present")
45 | }
46 |
47 | userAgentValue := matches[0][0]
48 | parts := strings.Split(userAgentValue, "/")
49 |
50 | deviceType := deviceTypeFromString(parts[1])
51 | if deviceType == DeviceTypeUnknown {
52 | return nil, errors.New("unhandled client type")
53 | }
54 |
55 | version, err := ParseVersion(parts[2])
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | return &UserAgent{
61 | DeviceType: deviceType,
62 | Version: *version,
63 | }, nil
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/grpc/headers/headers.go:
--------------------------------------------------------------------------------
1 | package headers
2 |
3 | // Type represents the header type
4 | type Type int
5 |
6 | const (
7 | // Outbound headers are only sent on the next service call. They are received as Inbound headers
8 | // on the recipients side.
9 | Outbound Type = iota
10 | // Inbound headers are received from the previous service call, and will not be sent to future service calls
11 | Inbound
12 | // Root headers are created from edge layers, and will be passed on to all future service calls
13 | Root
14 | // Propagating headers can be created by anyone, and will be passed on to all future service calls
15 | Propagating
16 | // ASCII headers are similar to Inbound headers, except the header is pure text and not an encoded protobuf
17 | ASCII
18 | )
19 |
20 | func (h Type) prefix() string {
21 | switch h {
22 | case Root:
23 | return "root-"
24 | case Propagating:
25 | return "prop-"
26 | default:
27 | return ""
28 | }
29 | }
30 |
31 | func (h Type) String() string {
32 | switch h {
33 | case Root:
34 | return "Root"
35 | case Propagating:
36 | return "Propagating"
37 | case Inbound:
38 | return "Inbound"
39 | case Outbound:
40 | return "Outbound"
41 | case ASCII:
42 | return "ASCII"
43 | default:
44 | return "Default"
45 | }
46 | }
47 |
48 | // Headers a custom map that takes a string as a key and either a string or []byte as its value
49 | type Headers map[string]interface{}
50 |
51 | // merge merges the current Headers h with the given merger Headers
52 | func (h Headers) merge(other Headers) {
53 | for k, v := range other {
54 | h[k] = v
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/grpc/protobuf/validation/test/Makefile:
--------------------------------------------------------------------------------
1 | all: generate
2 |
3 | generate:
4 | docker run --platform linux/amd64 -v $(shell pwd)/:/proto -v $(shell pwd)/:/genproto kinecosystem/protoc-gen-go
5 |
6 | .PHONY: all generate
7 |
--------------------------------------------------------------------------------
/pkg/grpc/protobuf/validation/test/test.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package test;
4 |
5 | option go_package=".;test";
6 |
7 | import "validate/validate.proto";
8 |
9 | service MyService {
10 | rpc Ping(PingRequest) returns (PingResponse) {}
11 | rpc PingStream(stream PingRequest) returns (stream PingResponse) {}
12 | }
13 |
14 | message PingRequest {
15 | int64 id = 1 [(validate.rules).int64 = {
16 | gt: 0
17 | lt: 100
18 | }];
19 | }
20 | message PingResponse {
21 | int64 id = 1 [(validate.rules).int64 = {
22 | gt: 0
23 | lt: 100
24 | }];
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/grpc/util_test.go:
--------------------------------------------------------------------------------
1 | package grpc
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestParseFullMethodName_ComponentExtraction(t *testing.T) {
11 | packageName, serviceName, methodName, err := ParseFullMethodName("/internal.v1.test.Service/Method")
12 | require.NoError(t, err)
13 |
14 | assert.Equal(t, "internal.v1.test", packageName)
15 | assert.Equal(t, "Service", serviceName)
16 | assert.Equal(t, "Method", methodName)
17 | }
18 |
19 | func TestParseFullMethodName_Validation(t *testing.T) {
20 | for _, invalidValue := range []string{
21 | "",
22 | "/internal.v1.test.Service",
23 | "/internal.v1.test.Service/",
24 | "internal.v1.test.Service/Method",
25 | "/internal.v1.test.Service./Method",
26 | "/Service/Method",
27 | } {
28 | _, _, _, err := ParseFullMethodName(invalidValue)
29 | assert.Error(t, err)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/lock/lock.go:
--------------------------------------------------------------------------------
1 | package lock
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | // Manager creates and manages locks. Locks produced for a given name
8 | // are re-entrant per Manager. This allows for implementations to
9 | // operate more efficiently (and safely).
10 | //
11 | // As locks are re-entrant per manager, it is strongly advised to use
12 | // a sync.Mutex (or some other state management mechanism) to coordinate
13 | // local concurrency.
14 | type Manager interface {
15 | // Create creates an unlocked DistributedLock for a specific key.
16 | Create(ctx context.Context, name string) (DistributedLock, error)
17 | }
18 |
19 | // DistributedLock is a handle to a distributed lock that spans across multiple
20 | // processes. Two DistributedLock's for the same name produced by the same Manager
21 | // are re-entrant. See Manager for details.
22 | type DistributedLock interface {
23 | // Acquire attempts to acquire the lock, blocking until the lock has been
24 | // successfully acquired.
25 | //
26 | // The returned channel is a channel that will be closed when the lock is lost.
27 | // The lock can be lost when the context is cancelled, Unlock() is called, or
28 | // the underlying implementation detects that the lock _might_ have been lost.
29 | Acquire(ctx context.Context) (<-chan struct{}, error)
30 |
31 | // Unlock unlocks the lock, if the lock is held.
32 | //
33 | // Unlock is idempotent.
34 | Unlock(ctx context.Context) error
35 |
36 | // IsLocked returns whether the lock is held by the process/manager.
37 | IsLocked() bool
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/metrics/constants.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | const (
4 | NewRelicContextKey = "newrelic_context"
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/metrics/events.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/newrelic/go-agent/v3/newrelic"
7 | )
8 |
9 | // RecordEvent records a new event with a name and set of key-value pairs
10 | func RecordEvent(ctx context.Context, eventName string, kvPairs map[string]interface{}) {
11 | nr, ok := ctx.Value(NewRelicContextKey).(*newrelic.Application)
12 | if ok {
13 | nr.RecordCustomEvent(eventName, kvPairs)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/metrics/metrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/newrelic/go-agent/v3/newrelic"
8 | )
9 |
10 | // RecordCount records a count metric
11 | func RecordCount(ctx context.Context, metricName string, count uint64) {
12 | nr, ok := ctx.Value(NewRelicContextKey).(*newrelic.Application)
13 | if ok {
14 | nr.RecordCustomMetric(metricName, float64(count))
15 | }
16 | }
17 |
18 | // RecordDuration records a duration metric
19 | func RecordDuration(ctx context.Context, metricName string, duration time.Duration) {
20 | nr, ok := ctx.Value(NewRelicContextKey).(*newrelic.Application)
21 | if ok {
22 | nr.RecordCustomMetric(metricName, float64(duration/time.Millisecond))
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/metrics/tracing.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/newrelic/go-agent/v3/newrelic"
8 | )
9 |
10 | // TraceMethodCall traces a method call with a given struct/package and method names
11 | func TraceMethodCall(ctx context.Context, structOrPackageName, methodName string) *MethodTracer {
12 | txn := newrelic.FromContext(ctx)
13 | if txn == nil {
14 | return nil
15 | }
16 |
17 | seg := txn.StartSegment(fmt.Sprintf("%s %s", structOrPackageName, methodName))
18 |
19 | return &MethodTracer{
20 | txn: txn,
21 | seg: seg,
22 | }
23 | }
24 |
25 | // MethodTracer collects analytics for a given method call within an existing
26 | // trace.
27 | type MethodTracer struct {
28 | txn *newrelic.Transaction
29 | seg *newrelic.Segment
30 | }
31 |
32 | // AddAttribute adds a key-value pair metadata to the method trace
33 | func (t *MethodTracer) AddAttribute(key string, value interface{}) {
34 | if t == nil {
35 | return
36 | }
37 |
38 | t.seg.AddAttribute(key, value)
39 | }
40 |
41 | // AddAttributes adds a set of key-value pair metadata to the method trace
42 | func (t *MethodTracer) AddAttributes(attributes map[string]interface{}) {
43 | if t == nil {
44 | return
45 | }
46 |
47 | for key, value := range attributes {
48 | t.seg.AddAttribute(key, value)
49 | }
50 | }
51 |
52 | // OnError observes an error within a method trace
53 | func (t *MethodTracer) OnError(err error) {
54 | if t == nil {
55 | return
56 | }
57 |
58 | if err == nil {
59 | return
60 | }
61 |
62 | t.txn.NoticeError(err)
63 | }
64 |
65 | // End completes the trace for the method call.
66 | func (t *MethodTracer) End() {
67 | if t == nil {
68 | return
69 | }
70 |
71 | t.seg.End()
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/netutil/domain.go:
--------------------------------------------------------------------------------
1 | package netutil
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | "golang.org/x/net/idna"
6 | )
7 |
8 | const (
9 | maxDomainNameSize = 253
10 | )
11 |
12 | // ValidateDomainName validates the string value as a domain name
13 | func ValidateDomainName(value string) error {
14 | if len(value) == 0 {
15 | return errors.New("domain name is empty")
16 | }
17 | if len(value) > maxDomainNameSize {
18 | return errors.New("domain name length exceeds limit")
19 | }
20 | if _, err := idna.Registration.ToASCII(value); err != nil {
21 | return errors.Wrap(err, "domain name is invalid")
22 | }
23 | return nil
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/netutil/ip.go:
--------------------------------------------------------------------------------
1 | package netutil
2 |
3 | import (
4 | "net"
5 | )
6 |
7 | // GetOutboundIP gets the locally preferred outbound IP address
8 | //
9 | // From https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
10 | func GetOutboundIP() net.IP {
11 | conn, err := net.Dial("udp", "8.8.8.8:80")
12 | if err != nil {
13 | panic(err)
14 | }
15 | defer conn.Close()
16 |
17 | localAddr := conn.LocalAddr().(*net.UDPAddr)
18 |
19 | return localAddr.IP
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/netutil/port.go:
--------------------------------------------------------------------------------
1 | package netutil
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | "strconv"
7 | )
8 |
9 | // GetAvailablePortForAddress returns an open port on the specified address
10 | func GetAvailablePortForAddress(address string) (int32, error) {
11 | server, err := net.Listen("tcp", fmt.Sprintf("%s:0", address))
12 | if err != nil {
13 | return 0, err
14 | }
15 | defer server.Close()
16 | _, portString, err := net.SplitHostPort(server.Addr().String())
17 | if err != nil {
18 | return 0, err
19 | }
20 | port, err := strconv.Atoi(portString)
21 | return int32(port), err
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/osutil/memory.go:
--------------------------------------------------------------------------------
1 | package osutil
2 |
3 | import (
4 | "io/ioutil"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/pbnjay/memory"
9 | )
10 |
11 | const (
12 | // This is the default value for cgroup's limit_in_bytes. This is not a
13 | // valid value and indicates that the memory is not restricted.
14 | // See https://unix.stackexchange.com/questions/420906/what-is-the-value-for-the-cgroups-limit-in-bytes-if-the-memory-is-not-restricte
15 | unrestrictedMemoryLimit = 9223372036854771712
16 | )
17 |
18 | const (
19 | dockerMemoryLimitLocation = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
20 | )
21 |
22 | // GetTotalMemory returns the total available memory size. The call is
23 | // container-aware.
24 | func GetTotalMemory() uint64 {
25 | totalMemory := memory.TotalMemory()
26 |
27 | cgroupLimit, err := ioutil.ReadFile(dockerMemoryLimitLocation)
28 | if err == nil {
29 | dockerMemoryLimit, err := strconv.ParseUint(strings.Replace(string(cgroupLimit), "\n", "", 1), 10, 64)
30 | if err == nil && dockerMemoryLimit != unrestrictedMemoryLimit {
31 | totalMemory = dockerMemoryLimit
32 | }
33 | }
34 | return totalMemory
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/rate/limiter.go:
--------------------------------------------------------------------------------
1 | package rate
2 |
3 | import (
4 | "sync"
5 |
6 | "golang.org/x/time/rate"
7 | )
8 |
9 | // Limiter limits operations based on a provided key.
10 | type Limiter interface {
11 | Allow(key string) (bool, error)
12 | }
13 |
14 | // LimiterCtor allows the creation of a Limiter using a provided rate.
15 | type LimiterCtor func(rate float64) Limiter
16 |
17 | type localRateLimiter struct {
18 | limit rate.Limit
19 |
20 | sync.Mutex
21 | limiters map[string]*rate.Limiter
22 | }
23 |
24 | // NewRedisRateLimiter returns an in memory limiter.
25 | func NewLocalRateLimiter(limit rate.Limit) Limiter {
26 | return &localRateLimiter{
27 | limit: limit,
28 | limiters: make(map[string]*rate.Limiter),
29 | }
30 | }
31 |
32 | // Allow implements limiter.Allow.
33 | func (l *localRateLimiter) Allow(key string) (bool, error) {
34 | l.Lock()
35 | limiter, ok := l.limiters[key]
36 | if !ok {
37 | limiter = rate.NewLimiter(l.limit, int(l.limit))
38 | l.limiters[key] = limiter
39 | }
40 | l.Unlock()
41 |
42 | return limiter.Allow(), nil
43 | }
44 |
45 | // NoLimiter never limits operations
46 | type NoLimiter struct {
47 | }
48 |
49 | // Allow implements limiter.Allow.
50 | func (n *NoLimiter) Allow(key string) (bool, error) {
51 | return true, nil
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/rate/limiter_test.go:
--------------------------------------------------------------------------------
1 | package rate
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "golang.org/x/time/rate"
8 | )
9 |
10 | func TestNoLimiter(t *testing.T) {
11 | l := &NoLimiter{}
12 | for i := 0; i < 10000; i++ {
13 | allowed, err := l.Allow("")
14 | assert.NoError(t, err)
15 | assert.True(t, allowed)
16 | }
17 | }
18 |
19 | func TestLocalRateLimiter(t *testing.T) {
20 | l := NewLocalRateLimiter(rate.Limit(2))
21 |
22 | for i := 0; i < 2; i++ {
23 | allowed, err := l.Allow("a")
24 | assert.NoError(t, err)
25 | assert.True(t, allowed)
26 | }
27 |
28 | allowed, err := l.Allow("a")
29 | assert.NoError(t, err)
30 | assert.False(t, allowed)
31 |
32 | // Ensure key partitioning is valid
33 | for i := 0; i < 2; i++ {
34 | allowed, err := l.Allow("b")
35 | assert.NoError(t, err)
36 | assert.True(t, allowed)
37 | }
38 |
39 | allowed, err = l.Allow("b")
40 | assert.NoError(t, err)
41 | assert.False(t, allowed)
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/retry/backoff/backoff.go:
--------------------------------------------------------------------------------
1 | // Package backoff provides varies backoff strategies for retry.
2 | package backoff
3 |
4 | import (
5 | "math"
6 | "time"
7 | )
8 |
9 | // Strategy is a function that provides the amount of time to wait before trying
10 | // again. Note: attempts starts at 1
11 | type Strategy func(attempts uint) time.Duration
12 |
13 | // Constant returns a strategy that always returns the provided duration.
14 | func Constant(interval time.Duration) Strategy {
15 | return func(attempts uint) time.Duration {
16 | return interval
17 | }
18 | }
19 |
20 | // Linear returns a strategy that linearly increases based off of the number of
21 | // attempts.
22 | //
23 | // delay = baseDelay * attempts
24 | // Ex. Linear(2*time.Seconds) = 2s, 4s, 6s, 8s, ...
25 | func Linear(baseDelay time.Duration) Strategy {
26 | return func(attempts uint) time.Duration {
27 | if delay := baseDelay * time.Duration(attempts); delay >= 0 {
28 | return delay
29 | }
30 |
31 | return math.MaxInt64
32 | }
33 | }
34 |
35 | // Exponential returns a strategy that exponentially increases based off of the
36 | // number of attempts.
37 | //
38 | // delay = baseDelay * base^(attempts - 1)
39 | // Ex. Exponential(2*time.Seconds, 3) = 2s, 6s, 18s, 54s, ...
40 | func Exponential(baseDelay time.Duration, base float64) Strategy {
41 | return func(attempts uint) time.Duration {
42 | if delay := baseDelay * time.Duration(math.Pow(base, float64(attempts-1))); delay >= 0 {
43 | return delay
44 | }
45 |
46 | return math.MaxInt64
47 | }
48 | }
49 |
50 | // BinaryExponential returns an Exponential strategy with a base of 2.0
51 | //
52 | // delay = baseDelay * 2^(attempts - 1)
53 | // Ex. BinaryExponential(2*time.Seconds) = 2s, 4s, 8s, 16s, ...
54 | func BinaryExponential(baseDelay time.Duration) Strategy {
55 | return Exponential(baseDelay, 2)
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/retry/backoff/backoff_test.go:
--------------------------------------------------------------------------------
1 | package backoff
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestConstant(t *testing.T) {
11 | s := Constant(100 * time.Millisecond)
12 |
13 | for i := uint(1); i < 10; i++ {
14 | assert.Equal(t, 100*time.Millisecond, s(i))
15 | }
16 | }
17 |
18 | func TestLinear(t *testing.T) {
19 | s := Linear(500 * time.Millisecond)
20 |
21 | assert.Equal(t, 500*time.Millisecond, s(1))
22 | assert.Equal(t, 1*time.Second, s(2))
23 | assert.Equal(t, 1*time.Second+500*time.Millisecond, s(3))
24 | assert.Equal(t, 2*time.Second, s(4))
25 | assert.Equal(t, 2*time.Second+500*time.Millisecond, s(5))
26 | }
27 |
28 | func TestExponential(t *testing.T) {
29 | s := Exponential(2*time.Second, 3.0)
30 |
31 | assert.Equal(t, 2*time.Second, s(1)) // 2*3^0
32 | assert.Equal(t, 6*time.Second, s(2)) // 2*3^1
33 | assert.Equal(t, 18*time.Second, s(3)) // 2*3^2
34 | assert.Equal(t, 54*time.Second, s(4)) // 2*3^3
35 | }
36 |
37 | func TestBinaryExponential(t *testing.T) {
38 | exp := Exponential(2*time.Second, 2)
39 | binExp := BinaryExponential(2 * time.Second)
40 |
41 | for i := uint(1); i < 10; i++ {
42 | assert.Equal(t, exp(i), binExp(i))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/solana/binary/utils.go:
--------------------------------------------------------------------------------
1 | package binary
2 |
3 | import (
4 | "crypto/ed25519"
5 | "encoding/binary"
6 | )
7 |
8 | func PutKey32(dst []byte, src []byte, offset *int) {
9 | copy(dst, src)
10 | *offset += ed25519.PublicKeySize
11 | }
12 |
13 | func PutOptionalKey32(dst []byte, src []byte, offset *int) {
14 | if len(src) > 0 {
15 | dst[0] = 1
16 | copy(dst[4:], src)
17 | }
18 |
19 | *offset += 4 + ed25519.PublicKeySize
20 | }
21 |
22 | func PutUint64(dst []byte, v uint64, offset *int) {
23 | binary.LittleEndian.PutUint64(dst, v)
24 | *offset += 8
25 | }
26 |
27 | func PutUint32(dst []byte, v uint32, offset *int) {
28 | binary.LittleEndian.PutUint32(dst, v)
29 | *offset += 4
30 | }
31 |
32 | func PutOptionalUint64(dst []byte, v *uint64, offset *int) {
33 | if v != nil {
34 | dst[0] = 1
35 | binary.LittleEndian.PutUint64(dst[4:], *v)
36 | }
37 | *offset += 4 + 8
38 | }
39 |
40 | func GetKey32(src []byte, dst *ed25519.PublicKey, offset *int) {
41 | *dst = make([]byte, ed25519.PublicKeySize)
42 | copy(*dst, src)
43 | *offset += ed25519.PublicKeySize
44 | }
45 |
46 | func GetOptionalKey32(src []byte, dst *ed25519.PublicKey, offset *int) {
47 | if src[0] == 1 {
48 | *dst = make([]byte, ed25519.PublicKeySize)
49 | copy(*dst, src[4:])
50 | }
51 | *offset += 4 + ed25519.PublicKeySize
52 | }
53 |
54 | func GetUint64(src []byte, dst *uint64, offset *int) {
55 | *dst = binary.LittleEndian.Uint64(src)
56 | *offset += 8
57 | }
58 |
59 | func GetUint32(src []byte, dst *uint32, offset *int) {
60 | *dst = binary.LittleEndian.Uint32(src)
61 | *offset += 4
62 | }
63 |
64 | func GetOptionalUint64(src []byte, dst **uint64, offset *int) {
65 | if src[0] == 1 {
66 | val := binary.LittleEndian.Uint64(src[4:])
67 | *dst = &val
68 | }
69 | *offset += 4 + 8
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/solana/client_test.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestSignatureStatus(t *testing.T) {
10 | zero, one := 0, 1
11 |
12 | testCases := []struct {
13 | s SignatureStatus
14 | confirmed bool
15 | finalized bool
16 | }{
17 | {
18 | s: SignatureStatus{
19 | Slot: 10,
20 | ErrorResult: nil,
21 | Confirmations: &zero,
22 | ConfirmationStatus: "",
23 | },
24 | },
25 | {
26 | s: SignatureStatus{
27 | Slot: 10,
28 | ErrorResult: nil,
29 | Confirmations: &zero,
30 | ConfirmationStatus: "random",
31 | },
32 | },
33 | {
34 | s: SignatureStatus{
35 | Slot: 10,
36 | ErrorResult: nil,
37 | Confirmations: &zero,
38 | ConfirmationStatus: confirmationStatusProcessed,
39 | },
40 | },
41 | {
42 | s: SignatureStatus{
43 | Slot: 10,
44 | ErrorResult: nil,
45 | Confirmations: &one,
46 | ConfirmationStatus: "",
47 | },
48 | confirmed: true,
49 | },
50 | {
51 | s: SignatureStatus{
52 | Slot: 10,
53 | ErrorResult: nil,
54 | Confirmations: &zero,
55 | ConfirmationStatus: confirmationStatusConfirmed,
56 | },
57 | confirmed: true,
58 | },
59 | {
60 | s: SignatureStatus{
61 | Slot: 10,
62 | ErrorResult: nil,
63 | Confirmations: &zero,
64 | ConfirmationStatus: confirmationStatusFinalized,
65 | },
66 | confirmed: true,
67 | finalized: true,
68 | },
69 | }
70 |
71 | for _, tc := range testCases {
72 | assert.Equal(t, tc.confirmed, tc.s.Confirmed())
73 | assert.Equal(t, tc.finalized, tc.s.Finalized())
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/solana/computebudget/program.go:
--------------------------------------------------------------------------------
1 | package compute_budget
2 |
3 | import (
4 | "crypto/ed25519"
5 | "encoding/binary"
6 | "errors"
7 |
8 | "github.com/stickychin/code-server/pkg/solana"
9 | )
10 |
11 | // ComputeBudget111111111111111111111111111111
12 | var ProgramKey = ed25519.PublicKey{3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0}
13 |
14 | const (
15 | commandRequestUnits uint8 = iota
16 | commandRequestHeapFrame
17 | commandSetComputeUnitLimit
18 | commandSetComputeUnitPrice
19 | )
20 |
21 | func SetComputeUnitLimit(computeUnitLimit uint32) solana.Instruction {
22 | data := make([]byte, 1+4)
23 | data[0] = commandSetComputeUnitLimit
24 | binary.LittleEndian.PutUint32(data[1:], computeUnitLimit)
25 |
26 | return solana.NewInstruction(
27 | ProgramKey[:],
28 | data,
29 | )
30 | }
31 |
32 | func SetComputeUnitPrice(computeUnitPrice uint64) solana.Instruction {
33 | data := make([]byte, 1+8)
34 | data[0] = commandSetComputeUnitPrice
35 | binary.LittleEndian.PutUint64(data[1:], computeUnitPrice)
36 |
37 | return solana.NewInstruction(
38 | ProgramKey[:],
39 | data,
40 | )
41 | }
42 |
43 | func DecompileSetComputeUnitLimitIxnData(data []byte) (uint32, error) {
44 | if len(data) != 5 {
45 | return 0, errors.New("invalid length")
46 | }
47 |
48 | if data[0] != commandSetComputeUnitLimit {
49 | return 0, errors.New("invalid instruction")
50 | }
51 |
52 | return binary.LittleEndian.Uint32(data[1:]), nil
53 | }
54 |
55 | func DecompileSetComputeUnitPriceIxnData(data []byte) (uint64, error) {
56 | if len(data) != 9 {
57 | return 0, errors.New("invalid length")
58 | }
59 |
60 | if data[0] != commandSetComputeUnitPrice {
61 | return 0, errors.New("invalid instruction")
62 | }
63 |
64 | return binary.LittleEndian.Uint64(data[1:]), nil
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/solana/config.go:
--------------------------------------------------------------------------------
1 | package solana
2 |
3 | type Environment string
4 |
5 | const (
6 | EnvironmentDev Environment = "https://api.devnet.solana.com"
7 | EnvironmentTest Environment = "https://api.testnet.solana.com"
8 | EnvironmentProd Environment = "https://api.mainnet-beta.solana.com"
9 | )
10 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/accounts_code_vm.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "bytes"
5 | "crypto/ed25519"
6 | "fmt"
7 |
8 | "github.com/mr-tron/base58"
9 | )
10 |
11 | const (
12 | CodeVmAccountSize = (8 + //discriminator
13 | 32 + // authority
14 | 32 + // mint
15 | 8 + // slot
16 | HashSize + // poh
17 | TokenPoolSize + // omnibus
18 | 1 + // lock_duration
19 | 1 + // bump
20 | 5) // padding
21 | )
22 |
23 | var CodeVmAccountDiscriminator = []byte{byte(AccountTypeCodeVm), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
24 |
25 | type CodeVmAccount struct {
26 | Authority ed25519.PublicKey
27 | Mint ed25519.PublicKey
28 | Slot uint64
29 | Poh Hash
30 | Omnibus TokenPool
31 | LockDuration uint8
32 | Bump uint8
33 | }
34 |
35 | func (obj *CodeVmAccount) Unmarshal(data []byte) error {
36 | if len(data) < CodeVmAccountSize {
37 | return ErrInvalidAccountData
38 | }
39 |
40 | var offset int
41 |
42 | var discriminator []byte
43 | getDiscriminator(data, &discriminator, &offset)
44 | if !bytes.Equal(discriminator, CodeVmAccountDiscriminator) {
45 | return ErrInvalidAccountData
46 | }
47 |
48 | getKey(data, &obj.Authority, &offset)
49 | getKey(data, &obj.Mint, &offset)
50 | getUint64(data, &obj.Slot, &offset)
51 | getHash(data, &obj.Poh, &offset)
52 | getTokenPool(data, &obj.Omnibus, &offset)
53 | getUint8(data, &obj.LockDuration, &offset)
54 | getUint8(data, &obj.Bump, &offset)
55 | offset += 5 // padding
56 |
57 | return nil
58 | }
59 |
60 | func (obj *CodeVmAccount) String() string {
61 | return fmt.Sprintf(
62 | "CodeVmAccount{authority=%s,mint=%s,slot=%d,poh=%s,omnibus=%s,lock_duration=%d,bump=%d}",
63 | base58.Encode(obj.Authority),
64 | base58.Encode(obj.Mint),
65 | obj.Slot,
66 | obj.Poh.String(),
67 | obj.Omnibus.String(),
68 | obj.LockDuration,
69 | obj.Bump,
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/accounts_storage.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | DefaultCompressedStateDepth = 20
5 |
6 | MaxStorageAccountNameSize = 32
7 | )
8 |
9 | // todo: Define account struct
10 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/accounts_unlock_state.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "bytes"
5 | "crypto/ed25519"
6 | "fmt"
7 |
8 | "github.com/mr-tron/base58"
9 | )
10 |
11 | const (
12 | UnlockStateAccountSize = (8 + //discriminator
13 | 32 + // vm
14 | 32 + // owner
15 | 32 + // address
16 | 1 + // bump
17 | 1 + // state
18 | 6) // padding
19 | )
20 |
21 | var UnlockStateAccountDiscriminator = []byte{byte(AccountTypeUnlockState), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
22 |
23 | type UnlockStateAccount struct {
24 | Vm ed25519.PublicKey
25 | Owner ed25519.PublicKey
26 | Address ed25519.PublicKey
27 | UnlockAt int64
28 | Bump uint8
29 | State TimelockState
30 | }
31 |
32 | func (obj *UnlockStateAccount) Unmarshal(data []byte) error {
33 | if len(data) < UnlockStateAccountSize {
34 | return ErrInvalidAccountData
35 | }
36 |
37 | var offset int
38 |
39 | var discriminator []byte
40 | getDiscriminator(data, &discriminator, &offset)
41 | if !bytes.Equal(discriminator, UnlockStateAccountDiscriminator) {
42 | return ErrInvalidAccountData
43 | }
44 |
45 | getKey(data, &obj.Vm, &offset)
46 | getKey(data, &obj.Owner, &offset)
47 | getKey(data, &obj.Address, &offset)
48 | getInt64(data, &obj.UnlockAt, &offset)
49 | getUint8(data, &obj.Bump, &offset)
50 | getTimelockState(data, &obj.State, &offset)
51 | offset += 6 // padding
52 |
53 | return nil
54 | }
55 |
56 | func (obj *UnlockStateAccount) IsUnlocked() bool {
57 | return obj.State == TimelockStateUnlocked
58 | }
59 |
60 | func (obj *UnlockStateAccount) String() string {
61 | return fmt.Sprintf(
62 | "UnlockStateAccount{vm=%s,owner=%s,address=%s,unlock_at=%d,bump=%d,state=%s",
63 | base58.Encode(obj.Vm),
64 | base58.Encode(obj.Owner),
65 | base58.Encode(obj.Address),
66 | obj.UnlockAt,
67 | obj.Bump,
68 | obj.State.String(),
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_compress.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | CompressInstructionArgsSize = (2 + // account_index
11 | SignatureSize) // signature
12 | )
13 |
14 | type CompressInstructionArgs struct {
15 | AccountIndex uint16
16 | Signature Signature
17 | }
18 |
19 | type CompressInstructionAccounts struct {
20 | VmAuthority ed25519.PublicKey
21 | Vm ed25519.PublicKey
22 | VmMemory ed25519.PublicKey
23 | VmStorage ed25519.PublicKey
24 | }
25 |
26 | func NewCompressInstruction(
27 | accounts *CompressInstructionAccounts,
28 | args *CompressInstructionArgs,
29 | ) solana.Instruction {
30 | var offset int
31 |
32 | // Serialize instruction arguments
33 | data := make([]byte, 1+CompressInstructionArgsSize)
34 |
35 | putCodeInstruction(data, CodeInstructionCompress, &offset)
36 | putUint16(data, args.AccountIndex, &offset)
37 | putSignature(data, args.Signature, &offset)
38 |
39 | return solana.Instruction{
40 | Program: PROGRAM_ADDRESS,
41 |
42 | // Instruction args
43 | Data: data,
44 |
45 | // Instruction accounts
46 | Accounts: []solana.AccountMeta{
47 | {
48 | PublicKey: accounts.VmAuthority,
49 | IsWritable: true,
50 | IsSigner: true,
51 | },
52 | {
53 | PublicKey: accounts.Vm,
54 | IsWritable: true,
55 | IsSigner: false,
56 | },
57 | {
58 | PublicKey: accounts.VmMemory,
59 | IsWritable: true,
60 | IsSigner: false,
61 | },
62 | {
63 | PublicKey: accounts.VmStorage,
64 | IsWritable: true,
65 | IsSigner: false,
66 | },
67 | },
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_init_nonce.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | InitNonceInstructionArgsSize = 2 // account_index
11 | )
12 |
13 | type InitNonceInstructionArgs struct {
14 | AccountIndex uint16
15 | }
16 |
17 | type InitNonceInstructionAccounts struct {
18 | VmAuthority ed25519.PublicKey
19 | Vm ed25519.PublicKey
20 | VmMemory ed25519.PublicKey
21 | VirtualAccountOwner ed25519.PublicKey
22 | }
23 |
24 | func NewInitNonceInstruction(
25 | accounts *InitNonceInstructionAccounts,
26 | args *InitNonceInstructionArgs,
27 | ) solana.Instruction {
28 | var offset int
29 |
30 | // Serialize instruction arguments
31 | data := make([]byte, 1+InitNonceInstructionArgsSize)
32 |
33 | putCodeInstruction(data, CodeInstructionInitNonce, &offset)
34 | putUint16(data, args.AccountIndex, &offset)
35 |
36 | return solana.Instruction{
37 | Program: PROGRAM_ADDRESS,
38 |
39 | // Instruction args
40 | Data: data,
41 |
42 | // Instruction accounts
43 | Accounts: []solana.AccountMeta{
44 | {
45 | PublicKey: accounts.VmAuthority,
46 | IsWritable: true,
47 | IsSigner: true,
48 | },
49 | {
50 | PublicKey: accounts.Vm,
51 | IsWritable: true,
52 | IsSigner: false,
53 | },
54 | {
55 | PublicKey: accounts.VmMemory,
56 | IsWritable: true,
57 | IsSigner: false,
58 | },
59 | {
60 | PublicKey: accounts.VirtualAccountOwner,
61 | IsWritable: false,
62 | IsSigner: false,
63 | },
64 | },
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_init_storage.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | MaxStorageAccountNameLength = 32
11 | )
12 |
13 | const (
14 | InitStorageInstructionArgsSize = (MaxStorageAccountNameLength + // name
15 | 1) // vm_storage_bump
16 | )
17 |
18 | type InitStorageInstructionArgs struct {
19 | Name string
20 | VmStorageBump uint8
21 | }
22 |
23 | type InitStorageInstructionAccounts struct {
24 | VmAuthority ed25519.PublicKey
25 | Vm ed25519.PublicKey
26 | VmStorage ed25519.PublicKey
27 | }
28 |
29 | func NewInitStorageInstruction(
30 | accounts *InitStorageInstructionAccounts,
31 | args *InitStorageInstructionArgs,
32 | ) solana.Instruction {
33 | var offset int
34 |
35 | // Serialize instruction arguments
36 | data := make([]byte, 1+InitStorageInstructionArgsSize)
37 |
38 | putCodeInstruction(data, CodeInstructionInitStorage, &offset)
39 | putFixedString(data, args.Name, MaxStorageAccountNameLength, &offset)
40 | putUint8(data, args.VmStorageBump, &offset)
41 |
42 | return solana.Instruction{
43 | Program: PROGRAM_ADDRESS,
44 |
45 | // Instruction args
46 | Data: data,
47 |
48 | // Instruction accounts
49 | Accounts: []solana.AccountMeta{
50 | {
51 | PublicKey: accounts.VmAuthority,
52 | IsWritable: true,
53 | IsSigner: true,
54 | },
55 | {
56 | PublicKey: accounts.Vm,
57 | IsWritable: true,
58 | IsSigner: false,
59 | },
60 | {
61 | PublicKey: accounts.VmStorage,
62 | IsWritable: true,
63 | IsSigner: false,
64 | },
65 | {
66 | PublicKey: SYSTEM_PROGRAM_ID,
67 | IsWritable: false,
68 | IsSigner: false,
69 | },
70 | {
71 | PublicKey: SYSVAR_RENT_PUBKEY,
72 | IsWritable: false,
73 | IsSigner: false,
74 | },
75 | },
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_init_unlock.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | InitUnlockInstructionArgsSize = 0
11 | )
12 |
13 | type InitUnlockInstructionArgs struct {
14 | }
15 |
16 | type InitUnlockInstructionAccounts struct {
17 | AccountOwner ed25519.PublicKey
18 | Payer ed25519.PublicKey
19 | Vm ed25519.PublicKey
20 | UnlockState ed25519.PublicKey
21 | }
22 |
23 | func NewInitUnlockInstruction(
24 | accounts *InitUnlockInstructionAccounts,
25 | args *InitUnlockInstructionArgs,
26 | ) solana.Instruction {
27 | var offset int
28 |
29 | // Serialize instruction arguments
30 | data := make([]byte, 1+InitUnlockInstructionArgsSize)
31 |
32 | putCodeInstruction(data, CodeInstructionInitUnlock, &offset)
33 |
34 | return solana.Instruction{
35 | Program: PROGRAM_ADDRESS,
36 |
37 | // Instruction args
38 | Data: data,
39 |
40 | // Instruction accounts
41 | Accounts: []solana.AccountMeta{
42 | {
43 | PublicKey: accounts.AccountOwner,
44 | IsWritable: true,
45 | IsSigner: true,
46 | },
47 | {
48 | PublicKey: accounts.Payer,
49 | IsWritable: true,
50 | IsSigner: true,
51 | },
52 | {
53 | PublicKey: accounts.Vm,
54 | IsWritable: true,
55 | IsSigner: false,
56 | },
57 | {
58 | PublicKey: accounts.UnlockState,
59 | IsWritable: true,
60 | IsSigner: false,
61 | },
62 | {
63 | PublicKey: SYSTEM_PROGRAM_ID,
64 | IsWritable: false,
65 | IsSigner: false,
66 | },
67 | {
68 | PublicKey: SYSVAR_RENT_PUBKEY,
69 | IsWritable: false,
70 | IsSigner: false,
71 | },
72 | },
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_resize_memory.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | ResizeMemoryInstructionArgsSize = 4 // account_size
11 | )
12 |
13 | type ResizeMemoryInstructionArgs struct {
14 | AccountSize uint32
15 | }
16 |
17 | type ResizeMemoryInstructionAccounts struct {
18 | VmAuthority ed25519.PublicKey
19 | Vm ed25519.PublicKey
20 | VmMemory ed25519.PublicKey
21 | }
22 |
23 | func NewResizeMemoryInstruction(
24 | accounts *ResizeMemoryInstructionAccounts,
25 | args *ResizeMemoryInstructionArgs,
26 | ) solana.Instruction {
27 | var offset int
28 |
29 | // Serialize instruction arguments
30 | data := make([]byte, 1+ResizeMemoryInstructionArgsSize)
31 |
32 | putCodeInstruction(data, CodeInstructionResizeMemory, &offset)
33 | putUint32(data, args.AccountSize, &offset)
34 |
35 | return solana.Instruction{
36 | Program: PROGRAM_ADDRESS,
37 |
38 | // Instruction args
39 | Data: data,
40 |
41 | // Instruction accounts
42 | Accounts: []solana.AccountMeta{
43 | {
44 | PublicKey: accounts.VmAuthority,
45 | IsWritable: true,
46 | IsSigner: true,
47 | },
48 | {
49 | PublicKey: accounts.Vm,
50 | IsWritable: true,
51 | IsSigner: false,
52 | },
53 | {
54 | PublicKey: accounts.VmMemory,
55 | IsWritable: true,
56 | IsSigner: false,
57 | },
58 | {
59 | PublicKey: SYSTEM_PROGRAM_ID,
60 | IsWritable: false,
61 | IsSigner: false,
62 | },
63 | {
64 | PublicKey: SYSVAR_RENT_PUBKEY,
65 | IsWritable: false,
66 | IsSigner: false,
67 | },
68 | },
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/instructions_snapshot.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | const (
10 | RelaySaveRecentRootInstructionArgsSize = 0
11 | )
12 |
13 | type RelaySaveRecentRootInstructionArgs struct {
14 | }
15 |
16 | type RelaySaveRecentRootInstructionAccounts struct {
17 | VmAuthority ed25519.PublicKey
18 | Vm ed25519.PublicKey
19 | Relay ed25519.PublicKey
20 | }
21 |
22 | func NewRelaySaveRecentRootInstruction(
23 | accounts *RelaySaveRecentRootInstructionAccounts,
24 | args *RelaySaveRecentRootInstructionArgs,
25 | ) solana.Instruction {
26 | var offset int
27 |
28 | // Serialize instruction arguments
29 | data := make([]byte, 1+RelaySaveRecentRootInstructionArgsSize)
30 |
31 | putCodeInstruction(data, CodeInstructionSnapshot, &offset)
32 |
33 | return solana.Instruction{
34 | Program: PROGRAM_ADDRESS,
35 |
36 | // Instruction args
37 | Data: data,
38 |
39 | // Instruction accounts
40 | Accounts: []solana.AccountMeta{
41 | {
42 | PublicKey: accounts.VmAuthority,
43 | IsWritable: true,
44 | IsSigner: true,
45 | },
46 | {
47 | PublicKey: accounts.Vm,
48 | IsWritable: true,
49 | IsSigner: false,
50 | },
51 | {
52 | PublicKey: accounts.Relay,
53 | IsWritable: true,
54 | IsSigner: false,
55 | },
56 | },
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/program.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 | "errors"
6 | )
7 |
8 | var (
9 | ErrInvalidProgram = errors.New("invalid program id")
10 | ErrInvalidAccountData = errors.New("unexpected account data")
11 | ErrInvalidVirtualAccountData = errors.New("unexpected virtual account data")
12 | ErrInvalidVirtualAccountType = errors.New("unexpected virtual account type")
13 | ErrInvalidInstructionData = errors.New("unexpected instruction data")
14 | )
15 |
16 | var (
17 | PROGRAM_ADDRESS = mustBase58Decode("vmZ1WUq8SxjBWcaeTCvgJRZbS84R61uniFsQy5YMRTJ")
18 | PROGRAM_ID = ed25519.PublicKey(PROGRAM_ADDRESS)
19 | )
20 |
21 | var (
22 | SYSTEM_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("11111111111111111111111111111111"))
23 | SPL_TOKEN_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"))
24 | TIMELOCK_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("time2Z2SCnn3qYg3ULKVtdkh8YmZ5jFdKicnA1W2YnJ"))
25 | SPLITTER_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("spLit2eb13Tz93if6aJM136nUWki5PVUsoEjcUjwpwW"))
26 |
27 | SYSVAR_RENT_PUBKEY = ed25519.PublicKey(mustBase58Decode("SysvarRent111111111111111111111111111111111"))
28 | )
29 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/transaction.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 | )
6 |
7 | func getOptionalAccountMetaAddress(account *ed25519.PublicKey) ed25519.PublicKey {
8 | if account != nil {
9 | return *account
10 | }
11 | return PROGRAM_ID
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_account_type.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type AccountType uint8
4 |
5 | const (
6 | AccountTypeUnknown AccountType = iota
7 | AccountTypeCodeVm
8 | AccountTypeMemory
9 | AccountTypeStorage
10 | AccountTypeRelay
11 | AccountTypeUnlockState
12 | AccountTypeWithdrawReceipt
13 | )
14 |
15 | func putAccountType(dst []byte, v AccountType, offset *int) {
16 | dst[*offset] = uint8(v)
17 | *offset += 1
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_code_instruction.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type CodeInstruction uint8
4 |
5 | const (
6 | Unknown CodeInstruction = iota
7 |
8 | CodeInstructionInitVm
9 | CodeInstructionInitMemory
10 | CodeInstructionInitStorage
11 | CodeInstructionInitRelay
12 | CodeInstructionInitNonce
13 | CodeInstructionInitTimelock
14 | CodeInstructionInitUnlock
15 |
16 | CodeInstructionExec
17 | CodeInstructionCompress
18 | CodeInstructionDecompress
19 |
20 | CodeInstructionResizeMemory
21 | CodeInstructionSnapshot
22 |
23 | CodeInstructionDeposit
24 | CodeInstructionWithdraw
25 | CodeInstructionUnlockI
26 | )
27 |
28 | func putCodeInstruction(dst []byte, v CodeInstruction, offset *int) {
29 | dst[*offset] = uint8(v)
30 | *offset += 1
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_hash.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "encoding/binary"
5 | "encoding/hex"
6 | "fmt"
7 | "strings"
8 | )
9 |
10 | const HashSize = 32
11 |
12 | type Hash [HashSize]byte
13 |
14 | func (h Hash) String() string {
15 | return hex.EncodeToString(h[:])
16 | }
17 |
18 | func putHash(dst []byte, v Hash, offset *int) {
19 | copy(dst[*offset:], v[:])
20 | *offset += HashSize
21 | }
22 | func getHash(src []byte, dst *Hash, offset *int) {
23 | copy(dst[:], src[*offset:])
24 | *offset += HashSize
25 | }
26 |
27 | type HashArray []Hash
28 |
29 | func getStaticHashArray(src []byte, dst *HashArray, length int, offset *int) {
30 | *dst = make([]Hash, length)
31 | for i := 0; i < int(length); i++ {
32 | getHash(src, &(*dst)[i], offset)
33 | }
34 | }
35 |
36 | func putHashArray(dst []byte, v HashArray, offset *int) {
37 | binary.LittleEndian.PutUint32(dst[*offset:], uint32(len(v)))
38 | *offset += 4
39 |
40 | for _, hash := range v {
41 | copy(dst[*offset:], hash[:])
42 | *offset += HashSize
43 | }
44 | }
45 |
46 | func (h HashArray) String() string {
47 | stringValues := make([]string, len(h))
48 | for i := 0; i < len(h); i++ {
49 | stringValues[i] = h[i].String()
50 | }
51 | return fmt.Sprintf("[%s]", strings.Join(stringValues, ","))
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_item_state.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type ItemState uint8
9 |
10 | const (
11 | ItemStateEmpty ItemState = iota
12 | ItemStateAllocated
13 | )
14 |
15 | const (
16 | ItemStateSize = 1
17 | )
18 |
19 | func getItemState(src []byte, dst *ItemState, offset *int) {
20 | *dst = ItemState(src[*offset])
21 | *offset += 1
22 | }
23 |
24 | func (obj ItemState) String() string {
25 | switch obj {
26 | case ItemStateEmpty:
27 | return "empty"
28 | case ItemStateAllocated:
29 | return "allocated"
30 | }
31 | return "empty"
32 | }
33 |
34 | type ItemStateArray []ItemState
35 |
36 | func getStaticItemStateArray(src []byte, dst *ItemStateArray, length int, offset *int) {
37 | *dst = make([]ItemState, length)
38 | for i := 0; i < int(length); i++ {
39 | getItemState(src, &(*dst)[i], offset)
40 | }
41 | }
42 |
43 | func (obj ItemStateArray) String() string {
44 | stringValues := make([]string, len(obj))
45 | for i := 0; i < len(obj); i++ {
46 | stringValues[i] = obj[i].String()
47 | }
48 | return fmt.Sprintf("[%s]", strings.Join(stringValues, ","))
49 | }
50 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_memory_allocator.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | MemoryV0NumAccounts = 32_000
5 | )
6 |
7 | type MemoryAllocator interface {
8 | IsAllocated(index int) bool
9 |
10 | Read(index int) ([]byte, bool)
11 |
12 | String() string
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_memory_layout.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type MemoryLayout uint8
4 |
5 | const (
6 | MemoryLayoutUnknown MemoryLayout = iota
7 | MemoryLayoutTimelock
8 | MemoryLayoutNonce
9 | MemoryLayoutRelay
10 | )
11 |
12 | func putMemoryLayout(dst []byte, v MemoryLayout, offset *int) {
13 | dst[*offset] = uint8(v)
14 | *offset += 1
15 | }
16 | func getMemoryLayout(src []byte, dst *MemoryLayout, offset *int) {
17 | *dst = MemoryLayout(src[*offset])
18 | *offset += 1
19 | }
20 |
21 | func GetPageSizeFromMemoryLayout(layout MemoryLayout) uint32 {
22 | switch layout {
23 | case MemoryLayoutNonce:
24 | return GetVirtualAccountSizeInMemory(VirtualAccountTypeDurableNonce)
25 | case MemoryLayoutTimelock:
26 | return GetVirtualAccountSizeInMemory(VirtualAccountTypeTimelock)
27 | case MemoryLayoutRelay:
28 | return GetVirtualAccountSizeInMemory(VirtualAccountTypeRelay)
29 | }
30 | return 0
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_memory_version.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type MemoryVersion uint8
4 |
5 | const (
6 | MemoryVersionV0 MemoryVersion = iota
7 | MemoryVersionV1
8 | )
9 |
10 | func putMemoryVersion(dst []byte, v MemoryVersion, offset *int) {
11 | dst[*offset] = uint8(v)
12 | *offset += 1
13 | }
14 | func getMemoryVersion(src []byte, dst *MemoryVersion, offset *int) {
15 | *dst = MemoryVersion(src[*offset])
16 | *offset += 1
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_merkle_tree.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import "fmt"
4 |
5 | const (
6 | minMerkleTreeSize = (HashSize + // root
7 | 8) // next_index
8 | )
9 |
10 | var (
11 | MerkleTreePrefix = []byte("merkletree")
12 | )
13 |
14 | type MerkleTree struct {
15 | Root Hash
16 | FilledSubtrees HashArray
17 | ZeroValues HashArray
18 | NextIndex uint64
19 | }
20 |
21 | func (obj *MerkleTree) Unmarshal(data []byte, levels int) error {
22 | if len(data) < GetMerkleTreeSize(levels) {
23 | return ErrInvalidAccountData
24 | }
25 |
26 | var offset int
27 |
28 | getHash(data, &obj.Root, &offset)
29 | getStaticHashArray(data, &obj.FilledSubtrees, levels, &offset)
30 | getStaticHashArray(data, &obj.ZeroValues, levels, &offset)
31 | getUint64(data, &obj.NextIndex, &offset)
32 |
33 | return nil
34 | }
35 |
36 | func (obj *MerkleTree) String() string {
37 | return fmt.Sprintf(
38 | "MerkleTree{root=%s,filled_subtrees=%s,zero_values=%s,next_index=%d}",
39 | obj.Root.String(),
40 | obj.FilledSubtrees.String(),
41 | obj.ZeroValues.String(),
42 | obj.NextIndex,
43 | )
44 | }
45 |
46 | func getMerkleTree(src []byte, dst *MerkleTree, levels int, offset *int) {
47 | dst.Unmarshal(src[*offset:], levels)
48 | *offset += GetMerkleTreeSize(int(levels))
49 | }
50 |
51 | func GetMerkleTreeSize(levels int) int {
52 | return (minMerkleTreeSize +
53 | levels*HashSize + // filled_subtrees
54 | levels*HashSize) // zero_values
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_opcode.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type Opcode uint8
4 |
5 | const (
6 | OpcodeUnknown Opcode = 0
7 |
8 | OpcodeTransfer Opcode = 11
9 | OpcodeWithdraw Opcode = 14
10 | OpcodeRelay Opcode = 21
11 |
12 | OpcodeExternalTransfer Opcode = 10
13 | OpcodeExternalWithdraw Opcode = 13
14 | OpcodeExternalRelay Opcode = 20
15 |
16 | OpcodeConditionalTransfer Opcode = 12
17 |
18 | OpcodeAirdrop Opcode = 30
19 | )
20 |
21 | func putOpcode(dst []byte, v Opcode, offset *int) {
22 | dst[*offset] = uint8(v)
23 | *offset += 1
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_recent_roots.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | const (
8 | minRecentRootsSize = (1 + // offset
9 | 1 + // num_items
10 | 6) // padding
11 | )
12 |
13 | type RecentRoots struct {
14 | Items HashArray
15 | Offset uint8
16 | NumItems uint8
17 | }
18 |
19 | func (obj *RecentRoots) Unmarshal(data []byte, length int) error {
20 | if len(data) < 1 {
21 | return ErrInvalidAccountData
22 | }
23 |
24 | var offset int
25 |
26 | getStaticHashArray(data, &obj.Items, length, &offset)
27 | getUint8(data, &obj.Offset, &offset)
28 | getUint8(data, &obj.NumItems, &offset)
29 | offset += 6 // padding
30 |
31 | return nil
32 | }
33 |
34 | func (obj *RecentRoots) String() string {
35 | return fmt.Sprintf(
36 | "RecentRoots{items=%s,offset=%d,num_items=%d}",
37 | obj.Items.String(),
38 | obj.Offset,
39 | obj.NumItems,
40 | )
41 | }
42 |
43 | func getRecentRoots(src []byte, dst *RecentRoots, length int, offset *int) {
44 | dst.Unmarshal(src[*offset:], length)
45 | *offset += GetRecentRootsSize(len(dst.Items))
46 | }
47 |
48 | func GetRecentRootsSize(length int) int {
49 | return (minRecentRootsSize +
50 | length*HashSize) // items
51 | }
52 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_signature.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "github.com/mr-tron/base58"
5 | )
6 |
7 | const SignatureSize = 64
8 |
9 | type Signature [SignatureSize]byte
10 |
11 | func (s Signature) String() string {
12 | return base58.Encode(s[:])
13 | }
14 |
15 | func putSignature(dst []byte, v Signature, offset *int) {
16 | copy(dst[*offset:], v[:])
17 | *offset += SignatureSize
18 | }
19 | func getSignature(src []byte, dst *Signature, offset *int) {
20 | copy(dst[:], src[*offset:])
21 | *offset += SignatureSize
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_timelock_state.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type TimelockState uint8
4 |
5 | const (
6 | TimelockStateUnknown TimelockState = iota
7 | TimelockStateUnlocked
8 | TimelockStateWaitingForTimeout
9 | )
10 |
11 | func getTimelockState(src []byte, dst *TimelockState, offset *int) {
12 | *dst = TimelockState(src[*offset])
13 | *offset += 1
14 | }
15 |
16 | func (s TimelockState) String() string {
17 | switch s {
18 | case TimelockStateUnlocked:
19 | return "unlocked"
20 | case TimelockStateWaitingForTimeout:
21 | return "waiting_for_timeout"
22 | }
23 | return "unknown"
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_token_pool.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 | "fmt"
6 |
7 | "github.com/mr-tron/base58"
8 | )
9 |
10 | const (
11 | TokenPoolSize = (32 + // vault
12 | 1) // vault_bump
13 | )
14 |
15 | type TokenPool struct {
16 | Vault ed25519.PublicKey
17 | VaultBump uint8
18 | }
19 |
20 | func (obj *TokenPool) Unmarshal(data []byte) error {
21 | if len(data) < TokenPoolSize {
22 | return ErrInvalidAccountData
23 | }
24 |
25 | var offset int
26 |
27 | getKey(data, &obj.Vault, &offset)
28 | getUint8(data, &obj.VaultBump, &offset)
29 |
30 | return nil
31 | }
32 |
33 | func (obj *TokenPool) String() string {
34 | return fmt.Sprintf(
35 | "TokenPool{vault=%s,vault_bump=%d}",
36 | base58.Encode(obj.Vault),
37 | obj.VaultBump,
38 | )
39 | }
40 |
41 | func getTokenPool(src []byte, dst *TokenPool, offset *int) {
42 | dst.Unmarshal(src[*offset:])
43 | *offset += TokenPoolSize
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/types_virtual_account_type.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import "errors"
4 |
5 | type VirtualAccountType uint8
6 |
7 | const (
8 | VirtualAccountTypeDurableNonce VirtualAccountType = iota
9 | VirtualAccountTypeTimelock
10 | VirtualAccountTypeRelay
11 | )
12 |
13 | func GetVirtualAccountTypeFromMemoryLayout(layout MemoryLayout) (VirtualAccountType, error) {
14 | switch layout {
15 | case MemoryLayoutNonce:
16 | return VirtualAccountTypeDurableNonce, nil
17 | case MemoryLayoutTimelock:
18 | return VirtualAccountTypeTimelock, nil
19 | case MemoryLayoutRelay:
20 | return VirtualAccountTypeRelay, nil
21 | default:
22 | return 0, errors.New("memory layout doesn't have a defined virtual account type")
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_accounts.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | func GetVirtualAccountSize(accountType VirtualAccountType) uint32 {
4 | switch accountType {
5 | case VirtualAccountTypeDurableNonce:
6 | return VirtualDurableNonceSize
7 | case VirtualAccountTypeRelay:
8 | return VirtualRelayAccountSize
9 | case VirtualAccountTypeTimelock:
10 | return VirtualTimelockAccountSize
11 | default:
12 | return 0
13 | }
14 | }
15 |
16 | func GetVirtualAccountSizeInMemory(accountType VirtualAccountType) uint32 {
17 | return GetVirtualAccountSize(accountType) + 1
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_accounts_durable_nonce.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 | "fmt"
6 |
7 | "github.com/mr-tron/base58"
8 | )
9 |
10 | const VirtualDurableNonceSize = (32 + // address
11 | 32) // hash
12 |
13 | type VirtualDurableNonce struct {
14 | Address ed25519.PublicKey
15 | Value Hash
16 | }
17 |
18 | func (obj *VirtualDurableNonce) Marshal() []byte {
19 | data := make([]byte, VirtualDurableNonceSize)
20 |
21 | var offset int
22 |
23 | putKey(data, obj.Address, &offset)
24 | putHash(data, obj.Value, &offset)
25 |
26 | return data
27 | }
28 |
29 | func (obj *VirtualDurableNonce) UnmarshalDirectly(data []byte) error {
30 | if len(data) < VirtualDurableNonceSize {
31 | return ErrInvalidVirtualAccountData
32 | }
33 |
34 | var offset int
35 |
36 | getKey(data, &obj.Address, &offset)
37 | getHash(data, &obj.Value, &offset)
38 |
39 | return nil
40 | }
41 |
42 | func (obj *VirtualDurableNonce) UnmarshalFromMemory(data []byte) error {
43 | if len(data) == 0 {
44 | return ErrInvalidVirtualAccountData
45 | }
46 |
47 | if data[0] != uint8(VirtualAccountTypeDurableNonce) {
48 | return ErrInvalidVirtualAccountType
49 | }
50 |
51 | return obj.UnmarshalDirectly(data[1:])
52 | }
53 |
54 | func (obj *VirtualDurableNonce) String() string {
55 | return fmt.Sprintf(
56 | "VirtualDurableNonce{address=%s,value=%s}",
57 | base58.Encode(obj.Address),
58 | obj.Value.String(),
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_accounts_relay_account.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | import (
4 | "crypto/ed25519"
5 | "fmt"
6 |
7 | "github.com/mr-tron/base58"
8 | )
9 |
10 | const VirtualRelayAccountSize = (32 + // target
11 | 32) // destination
12 |
13 | type VirtualRelayAccount struct {
14 | Target ed25519.PublicKey
15 | Destination ed25519.PublicKey
16 | }
17 |
18 | func (obj *VirtualRelayAccount) Marshal() []byte {
19 | data := make([]byte, VirtualRelayAccountSize)
20 |
21 | var offset int
22 |
23 | putKey(data, obj.Target, &offset)
24 | putKey(data, obj.Destination, &offset)
25 |
26 | return data
27 | }
28 |
29 | func (obj *VirtualRelayAccount) UnmarshalDirectly(data []byte) error {
30 | if len(data) < VirtualRelayAccountSize {
31 | return ErrInvalidVirtualAccountData
32 | }
33 |
34 | var offset int
35 |
36 | getKey(data, &obj.Target, &offset)
37 | getKey(data, &obj.Destination, &offset)
38 |
39 | return nil
40 | }
41 |
42 | func (obj *VirtualRelayAccount) UnmarshalFromMemory(data []byte) error {
43 | if len(data) == 0 {
44 | return ErrInvalidVirtualAccountData
45 | }
46 |
47 | if data[0] != uint8(VirtualAccountTypeRelay) {
48 | return ErrInvalidVirtualAccountType
49 | }
50 |
51 | return obj.UnmarshalDirectly(data[1:])
52 | }
53 |
54 | func (obj *VirtualRelayAccount) String() string {
55 | return fmt.Sprintf(
56 | "VirtualRelayAccount{target=%s,destination=%s}",
57 | base58.Encode(obj.Target),
58 | base58.Encode(obj.Destination),
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | type VirtualInstruction struct {
4 | Opcode Opcode
5 | Data []byte
6 | }
7 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_airdrop.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | AirdropVirtrualInstructionDataSize = (SignatureSize + // signature
5 | 8 + // amount
6 | 1) // count
7 | )
8 |
9 | type AirdropVirtualInstructionArgs struct {
10 | Amount uint64
11 | Count uint8
12 | Signature Signature
13 | }
14 |
15 | func NewAirdropVirtualInstruction(
16 | args *AirdropVirtualInstructionArgs,
17 | ) VirtualInstruction {
18 | var offset int
19 | data := make([]byte, AirdropVirtrualInstructionDataSize)
20 | putSignature(data, args.Signature, &offset)
21 | putUint64(data, args.Amount, &offset)
22 | putUint8(data, args.Count, &offset)
23 |
24 | return VirtualInstruction{
25 | Opcode: OpcodeAirdrop,
26 | Data: data,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_conditional_transfer.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | ConditionalTransferVirtrualInstructionDataSize = (SignatureSize + // signature
5 | 8) // amount
6 | )
7 |
8 | type ConditionalTransferVirtualInstructionArgs struct {
9 | Amount uint64
10 | Signature Signature
11 | }
12 |
13 | func NewConditionalTransferVirtualInstruction(
14 | args *ConditionalTransferVirtualInstructionArgs,
15 | ) VirtualInstruction {
16 | var offset int
17 | data := make([]byte, ConditionalTransferVirtrualInstructionDataSize)
18 | putSignature(data, args.Signature, &offset)
19 | putUint64(data, args.Amount, &offset)
20 |
21 | return VirtualInstruction{
22 | Opcode: OpcodeConditionalTransfer,
23 | Data: data,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_external_relay.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | ExternalRelayVirtrualInstructionDataSize = (8 + // amount
5 | HashSize + // transcript
6 | HashSize + // recent_root
7 | HashSize) // commitment
8 | )
9 |
10 | type ExternalRelayVirtualInstructionArgs struct {
11 | Amount uint64
12 | Transcript Hash
13 | RecentRoot Hash
14 | Commitment Hash
15 | }
16 |
17 | func NewExternalRelayVirtualInstruction(
18 | args *ExternalRelayVirtualInstructionArgs,
19 | ) VirtualInstruction {
20 | var offset int
21 | data := make([]byte, ExternalRelayVirtrualInstructionDataSize)
22 |
23 | putUint64(data, args.Amount, &offset)
24 | putHash(data, args.Transcript, &offset)
25 | putHash(data, args.RecentRoot, &offset)
26 | putHash(data, args.Commitment, &offset)
27 |
28 | return VirtualInstruction{
29 | Opcode: OpcodeExternalRelay,
30 | Data: data,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_external_transfer.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | ExternalTransferVirtrualInstructionDataSize = (SignatureSize + // signature
5 | 8) // amount
6 | )
7 |
8 | type ExternalTransferVirtualInstructionArgs struct {
9 | Amount uint64
10 | Signature Signature
11 | }
12 |
13 | func NewExternalTransferVirtualInstruction(
14 | args *TransferVirtualInstructionArgs,
15 | ) VirtualInstruction {
16 | var offset int
17 | data := make([]byte, ExternalTransferVirtrualInstructionDataSize)
18 | putSignature(data, args.Signature, &offset)
19 | putUint64(data, args.Amount, &offset)
20 |
21 | return VirtualInstruction{
22 | Opcode: OpcodeExternalTransfer,
23 | Data: data,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_external_withdraw.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | ExternalWithdrawVirtrualInstructionDataSize = SignatureSize // signature
5 | )
6 |
7 | type ExternalWithdrawVirtualInstructionArgs struct {
8 | Signature Signature
9 | }
10 |
11 | func NewExternalWithdrawVirtualInstruction(
12 | args *ExternalWithdrawVirtualInstructionArgs,
13 | ) VirtualInstruction {
14 | var offset int
15 | data := make([]byte, ExternalWithdrawVirtrualInstructionDataSize)
16 | putSignature(data, args.Signature, &offset)
17 |
18 | return VirtualInstruction{
19 | Opcode: OpcodeExternalWithdraw,
20 | Data: data,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_relay.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | RelayVirtrualInstructionDataSize = (8 + // amount
5 | HashSize + // transcript
6 | HashSize + // recent_root
7 | HashSize) // commitment
8 | )
9 |
10 | type RelayVirtualInstructionArgs struct {
11 | Amount uint64
12 | Transcript Hash
13 | RecentRoot Hash
14 | Commitment Hash
15 | }
16 |
17 | func NewRelayVirtualInstruction(
18 | args *RelayVirtualInstructionArgs,
19 | ) VirtualInstruction {
20 | var offset int
21 | data := make([]byte, RelayVirtrualInstructionDataSize)
22 |
23 | putUint64(data, args.Amount, &offset)
24 | putHash(data, args.Transcript, &offset)
25 | putHash(data, args.RecentRoot, &offset)
26 | putHash(data, args.Commitment, &offset)
27 |
28 | return VirtualInstruction{
29 | Opcode: OpcodeRelay,
30 | Data: data,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_transfer.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | TransferVirtrualInstructionDataSize = (SignatureSize + // signature
5 | 8) // amount
6 | )
7 |
8 | type TransferVirtualInstructionArgs struct {
9 | Amount uint64
10 | Signature Signature
11 | }
12 |
13 | func NewTransferVirtualInstruction(
14 | args *TransferVirtualInstructionArgs,
15 | ) VirtualInstruction {
16 | var offset int
17 | data := make([]byte, TransferVirtrualInstructionDataSize)
18 | putSignature(data, args.Signature, &offset)
19 | putUint64(data, args.Amount, &offset)
20 |
21 | return VirtualInstruction{
22 | Opcode: OpcodeTransfer,
23 | Data: data,
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/solana/cvm/virtual_instructions_withdraw.go:
--------------------------------------------------------------------------------
1 | package cvm
2 |
3 | const (
4 | WithdrawVirtrualInstructionDataSize = SignatureSize // signature
5 | )
6 |
7 | type WithdrawVirtualInstructionArgs struct {
8 | Signature Signature
9 | }
10 |
11 | func NewWithdrawVirtualInstruction(
12 | args *WithdrawVirtualInstructionArgs,
13 | ) VirtualInstruction {
14 | var offset int
15 | data := make([]byte, WithdrawVirtrualInstructionDataSize)
16 | putSignature(data, args.Signature, &offset)
17 |
18 | return VirtualInstruction{
19 | Opcode: OpcodeWithdraw,
20 | Data: data,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/solana/memo/program.go:
--------------------------------------------------------------------------------
1 | package memo
2 |
3 | import (
4 | "bytes"
5 | "crypto/ed25519"
6 |
7 | "github.com/pkg/errors"
8 |
9 | "github.com/stickychin/code-server/pkg/solana"
10 | )
11 |
12 | // ProgramKey is the address of the memo program that should be used.
13 | //
14 | // Current key: Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo
15 | //
16 | // todo: lock this in, or make configurable
17 | var ProgramKey = ed25519.PublicKey{5, 74, 83, 80, 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, 223, 30, 171, 171, 208, 166, 6, 120, 136, 73, 50, 244, 238, 246, 160}
18 |
19 | // Reference: https://github.com/solana-labs/solana-program-library/blob/master/memo/program/src/entrypoint.rs
20 | func Instruction(data string) solana.Instruction {
21 | return solana.NewInstruction(
22 | ProgramKey,
23 | []byte(data),
24 | )
25 | }
26 |
27 | type DecompiledMemo struct {
28 | Data []byte
29 | }
30 |
31 | func DecompileMemo(m solana.Message, index int) (*DecompiledMemo, error) {
32 | if index >= len(m.Instructions) {
33 | return nil, errors.Errorf("instruction doesn't exist at %d", index)
34 | }
35 |
36 | i := m.Instructions[index]
37 |
38 | if !bytes.Equal(m.Accounts[i.ProgramIndex], ProgramKey) {
39 | return nil, solana.ErrIncorrectProgram
40 | }
41 |
42 | return &DecompiledMemo{Data: i.Data}, nil
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/solana/memo/program_test.go:
--------------------------------------------------------------------------------
1 | package memo
2 |
3 | import (
4 | "crypto/ed25519"
5 | "testing"
6 |
7 | "github.com/stickychin/code-server/pkg/solana"
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestInstruction(t *testing.T) {
13 | i := Instruction("hello, world!")
14 | assert.Equal(t, ProgramKey, i.Program)
15 | assert.Empty(t, i.Accounts)
16 | assert.Equal(t, "hello, world!", string(i.Data))
17 | }
18 |
19 | func TestDecompile(t *testing.T) {
20 | tx := solana.NewTransaction(
21 | make([]byte, 32),
22 | Instruction("hello, world"),
23 | )
24 |
25 | i, err := DecompileMemo(tx.Message, 0)
26 | assert.NoError(t, err)
27 | assert.Equal(t, "hello, world", string(i.Data))
28 |
29 | _, err = DecompileMemo(tx.Message, 1)
30 | assert.Error(t, err)
31 | assert.Contains(t, err.Error(), "instruction doesn't exist")
32 |
33 | tx.Message.Accounts[1], _, err = ed25519.GenerateKey(nil)
34 | require.NoError(t, err)
35 | _, err = DecompileMemo(tx.Message, 0)
36 | assert.Error(t, err)
37 | assert.Equal(t, solana.ErrIncorrectProgram, err)
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/solana/shortvec/shortvec.go:
--------------------------------------------------------------------------------
1 | package shortvec
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "math"
7 | )
8 |
9 | // EncodeLen encodes the specified len into the writer.
10 | //
11 | // If len > math.MaxUint16, an error is returned.
12 | func EncodeLen(w io.Writer, len int) (n int, err error) {
13 | if len > math.MaxUint16 {
14 | return 0, fmt.Errorf("len exceeds %d", math.MaxUint16)
15 | }
16 |
17 | written := 0
18 | valBuf := make([]byte, 1)
19 |
20 | for {
21 | valBuf[0] = byte(len & 0x7f)
22 | len >>= 7
23 | if len == 0 {
24 | n, err := w.Write(valBuf)
25 | written += n
26 |
27 | return written, err
28 | }
29 |
30 | valBuf[0] |= 0x80
31 | n, err := w.Write(valBuf)
32 | written += n
33 | if err != nil {
34 | return written, err
35 | }
36 | }
37 | }
38 |
39 | // DecodeLen decodes a shortvec encoded len from the reader.
40 | func DecodeLen(r io.Reader) (val int, err error) {
41 | var offset int
42 | valBuf := make([]byte, 1)
43 |
44 | for {
45 | if _, err := r.Read(valBuf); err != nil {
46 | return 0, err
47 | }
48 |
49 | val |= int(valBuf[0]&0x7f) << (offset * 7)
50 | offset++
51 |
52 | if valBuf[0]&0x80 == 0 {
53 | break
54 | }
55 | }
56 |
57 | if offset > 3 {
58 | return 0, fmt.Errorf("invalid size: %d (max 3)", offset)
59 | }
60 |
61 | return val, nil
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/solana/shortvec/shortvec_test.go:
--------------------------------------------------------------------------------
1 | package shortvec
2 |
3 | import (
4 | "bytes"
5 | "math"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestShortVec_Valid(t *testing.T) {
13 | for i := 0; i < math.MaxUint16; i++ {
14 | buf := &bytes.Buffer{}
15 | _, err := EncodeLen(buf, i)
16 | require.NoError(t, err)
17 |
18 | actual, err := DecodeLen(buf)
19 | require.NoError(t, err)
20 | require.Equal(t, i, actual)
21 | }
22 | }
23 |
24 | func TestShortVec_CrossImpl(t *testing.T) {
25 | for _, tc := range []struct {
26 | val int
27 | encoded []byte
28 | }{
29 | {0x0, []byte{0x0}},
30 | {0x7f, []byte{0x7f}},
31 | {0x80, []byte{0x80, 0x01}},
32 | {0xff, []byte{0xff, 0x01}},
33 | {0x100, []byte{0x80, 0x02}},
34 | {0x7fff, []byte{0xff, 0xff, 0x01}},
35 | {0xffff, []byte{0xff, 0xff, 0x03}},
36 | } {
37 | buf := &bytes.Buffer{}
38 | n, err := EncodeLen(buf, tc.val)
39 | require.NoError(t, err)
40 | assert.Equal(t, len(tc.encoded), n)
41 | assert.Equal(t, tc.encoded, buf.Bytes())
42 | }
43 | }
44 |
45 | func TestShortVec_Invalid(t *testing.T) {
46 | _, err := EncodeLen(&bytes.Buffer{}, math.MaxUint16+1)
47 | require.NotNil(t, err)
48 | }
49 |
--------------------------------------------------------------------------------
/pkg/solana/splitter/errors.go:
--------------------------------------------------------------------------------
1 | package splitter_token
2 |
3 | type SplitterTokenError uint32
4 |
5 | const (
6 | // Invalid pool state for this instruction
7 | ErrInvalidPoolState SplitterTokenError = iota + 0x1770
8 |
9 | //Invalid commitment state for this instruction
10 | ErrInvalidCommitmentState
11 |
12 | //Invalid recent root value
13 | ErrInvalidRecentRoot
14 |
15 | //Invalid token account
16 | ErrInvalidVaultAccount
17 |
18 | //Insufficient vault funds
19 | ErrInsufficientVaultBalance
20 |
21 | //Invalid authority
22 | ErrInvalidAuthority
23 |
24 | //Invalid vault owner
25 | ErrInvalidVaultOwner
26 |
27 | //Merkle tree full
28 | ErrMerkleTreeFull
29 |
30 | //Invalid merkle tree depth
31 | ErrInvalidMerkleTreeDepth
32 |
33 | //Proof already verified
34 | ErrProofAlreadyVerified
35 |
36 | //Proof not verified
37 | ErrProofNotVerified
38 |
39 | //Invalid proof size
40 | ErrInvalidProofSize
41 |
42 | //Invalid proof
43 | ErrInvalidProof
44 | )
45 |
--------------------------------------------------------------------------------
/pkg/solana/splitter/legacy_test.go:
--------------------------------------------------------------------------------
1 | package splitter_token
2 |
3 | // todo: updated set of tests from production data
4 |
--------------------------------------------------------------------------------
/pkg/solana/splitter/program.go:
--------------------------------------------------------------------------------
1 | package splitter_token
2 |
3 | import (
4 | "crypto/ed25519"
5 | "errors"
6 | )
7 |
8 | var (
9 | ErrInvalidProgram = errors.New("invalid program id")
10 | ErrInvalidAccountData = errors.New("unexpected account data")
11 | ErrInvalidInstructionData = errors.New("unexpected instruction data")
12 | )
13 |
14 | var (
15 | PROGRAM_ADDRESS = mustBase58Decode("spLit2eb13Tz93if6aJM136nUWki5PVUsoEjcUjwpwW")
16 | PROGRAM_ID = ed25519.PublicKey(PROGRAM_ADDRESS)
17 | )
18 |
19 | var (
20 | SYSTEM_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("11111111111111111111111111111111"))
21 | SPL_TOKEN_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"))
22 | SPL_ASSOCIATED_TOKEN_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"))
23 |
24 | SYSVAR_CLOCK_PUBKEY = ed25519.PublicKey(mustBase58Decode("SysvarC1ock11111111111111111111111111111111"))
25 | SYSVAR_RENT_PUBKEY = ed25519.PublicKey(mustBase58Decode("SysvarRent111111111111111111111111111111111"))
26 | )
27 |
28 | // AccountMeta represents the account information required
29 | // for building transactions.
30 | type AccountMeta struct {
31 | PublicKey ed25519.PublicKey
32 | IsWritable bool
33 | IsSigner bool
34 | }
35 |
36 | // Instruction represents a transaction instruction.
37 | type Instruction struct {
38 | Program ed25519.PublicKey
39 | Accounts []AccountMeta
40 | Data []byte
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/solana/splitter/types.go:
--------------------------------------------------------------------------------
1 | package splitter_token
2 |
3 | type DataVersion uint8
4 |
5 | const (
6 | UnknownDataVersion DataVersion = iota
7 | DataVersion1
8 | )
9 |
10 | func putDataVersion(dst []byte, v DataVersion, offset *int) {
11 | putUint8(dst, uint8(v), offset)
12 | }
13 |
14 | func getDataVersion(src []byte, dst *DataVersion, offset *int) {
15 | var v uint8
16 | getUint8(src, &v, offset)
17 | *dst = DataVersion(v)
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/solana/splitter/types_hash.go:
--------------------------------------------------------------------------------
1 | package splitter_token
2 |
3 | import "fmt"
4 |
5 | const (
6 | // HashSize is the size, in bytes, of SHA256 hashes as used in this package.
7 | HashSize = 32
8 | )
9 |
10 | type Hash []byte
11 |
12 | func (h Hash) ToString() string {
13 | return fmt.Sprintf("%x", h)
14 | }
15 |
16 | func putHash(dst []byte, src []byte, offset *int) {
17 | copy(dst[*offset:], src)
18 | *offset += HashSize
19 | }
20 |
21 | func getHash(src []byte, dst *Hash, offset *int) {
22 | *dst = make([]byte, HashSize)
23 | copy(*dst, src[*offset:])
24 | *offset += HashSize
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/address.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | var (
10 | preSwapStatePrefix = []byte("pre_swap_state")
11 | )
12 |
13 | type GetPreSwapStateAddressArgs struct {
14 | Source ed25519.PublicKey
15 | Destination ed25519.PublicKey
16 | Nonce ed25519.PublicKey
17 | }
18 |
19 | func GetPreSwapStateAddress(args *GetPreSwapStateAddressArgs) (ed25519.PublicKey, uint8, error) {
20 | return solana.FindProgramAddressAndBump(
21 | PROGRAM_ID,
22 | preSwapStatePrefix,
23 | args.Source,
24 | args.Destination,
25 | args.Nonce,
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/address_test.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mr-tron/base58"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | // Tests based on account created here https://solscan.io/tx/4kXywqQRqJXRnm8mzAVRYkL1YVwyAEi8rVVNzRYG3AAu73xqjxNdHKX6LgoFhon66ZWHJ8HU3QkAmiot7AGzXPzd
12 |
13 | func TestPreSwapStateAddress(t *testing.T) {
14 | address, _, err := GetPreSwapStateAddress(&GetPreSwapStateAddressArgs{
15 | Source: mustBase58Decode("5nNBW1KhzHVbR4NMPLYPRYj3UN5vgiw5GrtpdK6eGoce"),
16 | Destination: mustBase58Decode("9Rgx4kjnYZBbeXXgbbYLT2FfgzrNHFUShDtp8dpHHjd2"),
17 | Nonce: mustBase58Decode("3SVPEF5HDcKLhVfKeAnbH5Azpyeuk2yyVjEjZbz4VhrL"),
18 | })
19 | require.NoError(t, err)
20 | assert.Equal(t, "Hh338LHJhkzPbDisGt5Lge8qkgc3RExvH7BdmKgnRQw9", base58.Encode(address))
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/errors.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | type SwapValidatorError uint32
4 |
5 | const (
6 | // Invalid input token amount sent
7 | InvalidInputTokenAmountSent SwapValidatorError = iota + 0x1770
8 |
9 | // Invalid output token amount received
10 | InvalidOutputTokenAmountReceived
11 |
12 | // Unexpected writable user account
13 | UnexpectedWritableUserAccount
14 |
15 | // Unexpected update to user token account
16 | UnexpectedUserTokenAccountUpdate
17 | )
18 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/instructions_post_swap.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import (
4 | "crypto/ed25519"
5 | )
6 |
7 | var postSwapInstructionDiscriminator = []byte{
8 | 0x9f, 0xd5, 0xb7, 0x39, 0xb3, 0x8a, 0x75, 0xa1,
9 | }
10 |
11 | const (
12 | PostSwapInstructionArgsSize = (1 + // StateBump
13 | 8 + // MaxToSend
14 | 8) // MinToReceive
15 | )
16 |
17 | type PostSwapInstructionArgs struct {
18 | StateBump uint8
19 | MaxToSend uint64
20 | MinToReceive uint64
21 | }
22 |
23 | type PostSwapInstructionAccounts struct {
24 | PreSwapState ed25519.PublicKey
25 | Source ed25519.PublicKey
26 | Destination ed25519.PublicKey
27 | Payer ed25519.PublicKey
28 | }
29 |
30 | func NewPostSwapInstruction(
31 | accounts *PostSwapInstructionAccounts,
32 | args *PostSwapInstructionArgs,
33 | ) Instruction {
34 | var offset int
35 |
36 | // Serialize instruction arguments
37 | data := make([]byte,
38 | len(postSwapInstructionDiscriminator)+
39 | PostSwapInstructionArgsSize)
40 |
41 | putDiscriminator(data, postSwapInstructionDiscriminator, &offset)
42 | putUint8(data, args.StateBump, &offset)
43 | putUint64(data, args.MaxToSend, &offset)
44 | putUint64(data, args.MinToReceive, &offset)
45 |
46 | return Instruction{
47 | Program: PROGRAM_ADDRESS,
48 |
49 | // Instruction args
50 | Data: data,
51 |
52 | // Instruction accounts
53 | Accounts: []AccountMeta{
54 | {
55 | PublicKey: accounts.PreSwapState,
56 | IsWritable: true,
57 | IsSigner: false,
58 | },
59 | {
60 | PublicKey: accounts.Source,
61 | IsWritable: false,
62 | IsSigner: false,
63 | },
64 | {
65 | PublicKey: accounts.Destination,
66 | IsWritable: false,
67 | IsSigner: false,
68 | },
69 | {
70 | PublicKey: accounts.Payer,
71 | IsWritable: true,
72 | IsSigner: true,
73 | },
74 | },
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/legacy.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import "github.com/stickychin/code-server/pkg/solana"
4 |
5 | func (i Instruction) ToLegacyInstruction() solana.Instruction {
6 | legacyAccountMeta := make([]solana.AccountMeta, len(i.Accounts))
7 | for i, accountMeta := range i.Accounts {
8 | legacyAccountMeta[i] = solana.AccountMeta{
9 | PublicKey: accountMeta.PublicKey,
10 | IsSigner: accountMeta.IsSigner,
11 | IsWritable: accountMeta.IsWritable,
12 | }
13 | }
14 |
15 | return solana.Instruction{
16 | Program: PROGRAM_ID,
17 | Accounts: legacyAccountMeta,
18 | Data: i.Data,
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/program.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import (
4 | "crypto/ed25519"
5 | "errors"
6 | )
7 |
8 | var (
9 | ErrInvalidProgram = errors.New("invalid program id")
10 | ErrInvalidAccountData = errors.New("unexpected account data")
11 | ErrInvalidInstructionData = errors.New("unexpected instruction data")
12 | )
13 |
14 | var (
15 | PROGRAM_ADDRESS = mustBase58Decode("sWvA66HNNvgamibZe88v3NN5nQwE8tp3KitfViFjukA")
16 | PROGRAM_ID = ed25519.PublicKey(PROGRAM_ADDRESS)
17 | )
18 |
19 | var (
20 | SYSTEM_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("11111111111111111111111111111111"))
21 |
22 | SYSVAR_RENT_PUBKEY = ed25519.PublicKey(mustBase58Decode("SysvarRent111111111111111111111111111111111"))
23 | )
24 |
25 | // AccountMeta represents the account information required
26 | // for building transactions.
27 | type AccountMeta struct {
28 | PublicKey ed25519.PublicKey
29 | IsWritable bool
30 | IsSigner bool
31 | }
32 |
33 | // Instruction represents a transaction instruction.
34 | type Instruction struct {
35 | Program ed25519.PublicKey
36 | Accounts []AccountMeta
37 | Data []byte
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/solana/swapvalidator/utils.go:
--------------------------------------------------------------------------------
1 | package swap_validator
2 |
3 | import (
4 | "crypto/ed25519"
5 | "encoding/binary"
6 |
7 | "github.com/mr-tron/base58"
8 | )
9 |
10 | // This file should be replaced with something more robust. Modified version of what the original agora and code pkgs do.
11 |
12 | func putDiscriminator(dst []byte, src []byte, offset *int) {
13 | copy(dst[*offset:], src)
14 | *offset += 8
15 | }
16 | func getDiscriminator(src []byte, dst *[]byte, offset *int) {
17 | *dst = make([]byte, 8)
18 | copy(*dst, src[*offset:])
19 | *offset += 8
20 | }
21 |
22 | func putKey(dst []byte, src []byte, offset *int) {
23 | copy(dst[*offset:], src)
24 | *offset += ed25519.PublicKeySize
25 | }
26 | func getKey(src []byte, dst *ed25519.PublicKey, offset *int) {
27 | *dst = make([]byte, ed25519.PublicKeySize)
28 | copy(*dst, src[*offset:])
29 | *offset += ed25519.PublicKeySize
30 | }
31 |
32 | func putUint8(dst []byte, v uint8, offset *int) {
33 | dst[*offset] = v
34 | *offset += 1
35 | }
36 | func getUint8(src []byte, dst *uint8, offset *int) {
37 | *dst = src[*offset]
38 | *offset += 1
39 | }
40 |
41 | func putUint64(dst []byte, v uint64, offset *int) {
42 | binary.LittleEndian.PutUint64(dst[*offset:], v)
43 | *offset += 8
44 | }
45 | func getUint64(src []byte, dst *uint64, offset *int) {
46 | *dst = binary.LittleEndian.Uint64(src[*offset:])
47 | *offset += 8
48 | }
49 |
50 | func mustBase58Decode(value string) []byte {
51 | decoded, err := base58.Decode(value)
52 | if err != nil {
53 | panic(err)
54 | }
55 | return decoded
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/solana/system/sysvar.go:
--------------------------------------------------------------------------------
1 | package system
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/mr-tron/base58/base58"
7 | )
8 |
9 | // https://explorer.solana.com/address/11111111111111111111111111111111
10 | var SystemAccount ed25519.PublicKey
11 |
12 | // RentSysVar points to the system variable "Rent"
13 | //
14 | // Source: https://github.com/solana-labs/solana/blob/f02a78d8fff2dd7297dc6ce6eb5a68a3002f5359/sdk/src/sysvar/rent.rs#L11
15 | var RentSysVar ed25519.PublicKey
16 |
17 | // RecentBlockhashesSysVar points to the system variable "Recent Blockhashes"
18 | //
19 | // Source: https://github.com/solana-labs/solana/blob/f02a78d8fff2dd7297dc6ce6eb5a68a3002f5359/sdk/src/sysvar/recent_blockhashes.rs#L12-L15
20 | var RecentBlockhashesSysVar ed25519.PublicKey
21 |
22 | func init() {
23 | var err error
24 |
25 | RentSysVar, err = base58.Decode("SysvarRent111111111111111111111111111111111")
26 | if err != nil {
27 | panic(err)
28 | }
29 |
30 | RecentBlockhashesSysVar, err = base58.Decode("SysvarRecentB1ockHashes11111111111111111111")
31 | if err != nil {
32 | panic(err)
33 | }
34 |
35 | SystemAccount, err = base58.Decode("11111111111111111111111111111111")
36 | if err != nil {
37 | panic(err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/address.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | import (
4 | "crypto/ed25519"
5 | "encoding/binary"
6 |
7 | "github.com/stickychin/code-server/pkg/solana"
8 | )
9 |
10 | const (
11 | expectedInitOffset = 0
12 | pdaVersion1 = 1
13 | )
14 |
15 | var (
16 | statePrefix = []byte("timelock_state")
17 | vaultPrefix = []byte("timelock_vault")
18 |
19 | pdaPadding = make([]byte, ed25519.PublicKeySize)
20 | )
21 |
22 | type GetStateAddressArgs struct {
23 | Mint ed25519.PublicKey
24 | TimeAuthority ed25519.PublicKey
25 | Nonce ed25519.PublicKey
26 | VaultOwner ed25519.PublicKey
27 | UnlockDuration uint64
28 | }
29 |
30 | type GetVaultAddressArgs struct {
31 | State ed25519.PublicKey
32 | }
33 |
34 | func GetStateAddress(args *GetStateAddressArgs) (ed25519.PublicKey, uint8, error) {
35 | unlockDurationBytes := make([]byte, 8)
36 | binary.LittleEndian.PutUint64(unlockDurationBytes, args.UnlockDuration)
37 |
38 | return solana.FindProgramAddressAndBump(
39 | PROGRAM_ID,
40 | statePrefix,
41 | []byte{pdaVersion1},
42 | args.Mint,
43 | args.TimeAuthority,
44 | args.Nonce,
45 | args.VaultOwner,
46 | unlockDurationBytes,
47 | pdaPadding,
48 | )
49 | }
50 |
51 | func GetVaultAddress(args *GetVaultAddressArgs) (ed25519.PublicKey, uint8, error) {
52 | return solana.FindProgramAddressAndBump(
53 | PROGRAM_ID,
54 | vaultPrefix,
55 | args.State,
56 | []byte{expectedInitOffset},
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/address_test.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mr-tron/base58"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | // todo: Base these on real-world examples from mainnet
12 |
13 | func TestGetStateAddress(t *testing.T) {
14 | address, _, err := GetStateAddress(&GetStateAddressArgs{
15 | Mint: mustBase58Decode("2U7X7s9CjKzkB9AqtJ4Am6hqiFhzPzisdEo3uMwuoPrq"),
16 | TimeAuthority: mustBase58Decode("DpnXnwD9DcBnMxpbNM36cko3m41qgrsopqTNsUesMuyd"),
17 | Nonce: mustBase58Decode("11111111111111111111111111111111"),
18 | VaultOwner: mustBase58Decode("9N3Ypqi8FwNBK21bFbpysVqTupiH13JRQg9wQ4xnjYxK"),
19 | UnlockDuration: 1814400,
20 | })
21 | require.NoError(t, err)
22 | assert.Equal(t, "7BJrsePGaFkkYWuzy63VGy7N5ZA9Gou1BZHETCPRydiY", base58.Encode(address))
23 | }
24 |
25 | func TestGetVaultAddress(t *testing.T) {
26 | address, _, err := GetVaultAddress(&GetVaultAddressArgs{
27 | State: mustBase58Decode("7BJrsePGaFkkYWuzy63VGy7N5ZA9Gou1BZHETCPRydiY"),
28 | })
29 | require.NoError(t, err)
30 | assert.Equal(t, "9e6MBQRfQLQmaheuvixLvsTtZfPMw4L4BX2x9LTj2kf3", base58.Encode(address))
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/constants.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | import "time"
4 |
5 | const (
6 | // Need to be very careful changing this value, as it's used in the state
7 | // address PDA.
8 | DefaultUnlockDuration = uint64(3 * 7 * 24 * time.Hour / time.Second) // 3 weeks in seconds
9 | )
10 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/errors.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | type TimelockTokenError uint32
4 |
5 | const (
6 | // Invalid timelock state for this instruction
7 | ErrInvalidTimelockState TimelockTokenError = iota + 0x1770
8 |
9 | // Invalid timelock duration provided
10 | ErrInvalidTimelockDuration
11 |
12 | // Invalid vault account
13 | ErrInvalidVaultAccount
14 |
15 | // The timelock period has not yet been reached
16 | ErrInsufficientTimeElapsed
17 |
18 | // Insufficient vault funds
19 | ErrInsufficientVaultBalance
20 |
21 | // Invalid time authority
22 | ErrInvalidTimeAuthority
23 |
24 | // Invalid vault owner
25 | ErrInvalidVaultOwner
26 |
27 | // Invalid close authority
28 | ErrInvalidCloseAuthority
29 |
30 | // Invalid token balance. Token balance must be zero.
31 | ErrNonZeroTokenBalance
32 | )
33 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/legacy_test.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | // todo: updated set of tests from production data
4 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/legacy_2022/types.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | type TimelockState uint8
4 |
5 | const (
6 | Unknown TimelockState = iota
7 | Unlocked
8 | WaitingForTimeout
9 | Locked
10 | Closed
11 | )
12 |
13 | func putTimelockAccountState(dst []byte, v TimelockState, offset *int) {
14 | putUint8(dst, uint8(v), offset)
15 | }
16 |
17 | func getTimelockAccountState(src []byte, dst *TimelockState, offset *int) {
18 | var v uint8
19 | getUint8(src, &v, offset)
20 | *dst = TimelockState(v)
21 | }
22 |
23 | func (s TimelockState) String() string {
24 | switch s {
25 | case Unknown:
26 | return "unknown"
27 | case Locked:
28 | return "locked"
29 | case WaitingForTimeout:
30 | return "waiting_for_timeout"
31 | case Unlocked:
32 | return "unlocked"
33 | case Closed:
34 | return "closed"
35 | default:
36 | return "unknown"
37 | }
38 | }
39 |
40 | type TimelockDataVersion uint8
41 |
42 | const (
43 | UnknownDataVersion TimelockDataVersion = iota
44 | DataVersion1
45 | )
46 |
47 | func putTimelockAccountDataVersion(dst []byte, v TimelockDataVersion, offset *int) {
48 | putUint8(dst, uint8(v), offset)
49 | }
50 |
51 | func getTimelockAccountDataVersion(src []byte, dst *TimelockDataVersion, offset *int) {
52 | var v uint8
53 | getUint8(src, &v, offset)
54 | *dst = TimelockDataVersion(v)
55 | }
56 |
57 | type TimelockPdaVersion uint8
58 |
59 | const (
60 | UnknownPdaVersion TimelockPdaVersion = iota
61 | PdaVersion1
62 | )
63 |
64 | func putTimelockAccountPdaVersion(dst []byte, v TimelockPdaVersion, offset *int) {
65 | putUint8(dst, uint8(v), offset)
66 | }
67 |
68 | func getTimelockAccountPdaVersion(src []byte, dst *TimelockPdaVersion, offset *int) {
69 | var v uint8
70 | getUint8(src, &v, offset)
71 | *dst = TimelockPdaVersion(v)
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/address.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | import (
4 | "crypto/ed25519"
5 |
6 | "github.com/stickychin/code-server/pkg/solana"
7 | )
8 |
9 | var (
10 | statePrefix = []byte("timelock_state")
11 | vaultPrefix = []byte("timelock_vault")
12 | )
13 |
14 | type GetStateAddressArgs struct {
15 | Mint ed25519.PublicKey
16 | TimeAuthority ed25519.PublicKey
17 | VaultOwner ed25519.PublicKey
18 | NumDaysLocked uint8
19 | }
20 |
21 | type GetVaultAddressArgs struct {
22 | State ed25519.PublicKey
23 | DataVersion TimelockDataVersion
24 | }
25 |
26 | func GetStateAddress(args *GetStateAddressArgs) (ed25519.PublicKey, uint8, error) {
27 | return solana.FindProgramAddressAndBump(
28 | PROGRAM_ID,
29 | statePrefix,
30 | args.Mint,
31 | args.TimeAuthority,
32 | args.VaultOwner,
33 | []byte{args.NumDaysLocked},
34 | )
35 | }
36 |
37 | func GetVaultAddress(args *GetVaultAddressArgs) (ed25519.PublicKey, uint8, error) {
38 | return solana.FindProgramAddressAndBump(
39 | PROGRAM_ID,
40 | vaultPrefix,
41 | args.State,
42 | []byte{byte(args.DataVersion)},
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/address_test.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mr-tron/base58/base58"
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | // Tests based on account created here https://explorer.solana.com/tx/2TTGdkrxuzQXe227CukjRMbppELELWyip9HQB2swuttgtfQovVp7bAXxSpBQjZpMpEWh17UFrwU1EYwiqYX5wK3
12 |
13 | func TestGetStateAddress(t *testing.T) {
14 | address, _, err := GetStateAddress(&GetStateAddressArgs{
15 | Mint: mustBase58Decode("kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6"),
16 | TimeAuthority: mustBase58Decode("codeHy87wGD5oMRLG75qKqsSi1vWE3oxNyYmXo5F9YR"),
17 | VaultOwner: mustBase58Decode("BuAprBZugjXG6QRbRQN8QKF8EzbW5SigkDuyR9KtqN5z"),
18 | NumDaysLocked: DefaultNumDaysLocked,
19 | })
20 | require.NoError(t, err)
21 | assert.Equal(t, "7Ema8Z4gAUWegampp2AuX4cvaTRy3VMwJUq8LMJshQTV", base58.Encode(address))
22 | }
23 |
24 | func TestGetVaultAddress(t *testing.T) {
25 | address, _, err := GetVaultAddress(&GetVaultAddressArgs{
26 | State: mustBase58Decode("7Ema8Z4gAUWegampp2AuX4cvaTRy3VMwJUq8LMJshQTV"),
27 | DataVersion: DataVersion1,
28 | })
29 | require.NoError(t, err)
30 | assert.Equal(t, "3538bYdWoRXUgBbyAyvG3Zemmawh75nmCQEvWc9DfKFR", base58.Encode(address))
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/constants.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | const (
4 | // Need to be very careful changing this value, as it's used in the state
5 | // address PDA.
6 | DefaultNumDaysLocked = uint8(21)
7 | )
8 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/errors.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | type TimeLockTokenError uint32
4 |
5 | const (
6 | // Invalid time-lock state for this instruction
7 | ErrInvalidTimeLockState TimeLockTokenError = iota + 0x1770
8 |
9 | // Invalid time-lock duration provided
10 | ErrInvalidTimeLockDuration
11 |
12 | // Invalid vault account
13 | ErrInvalidVaultAccount
14 |
15 | // The time-lock period has not yet been reached
16 | ErrInsufficientTimeElapsed
17 |
18 | // Insufficient vault funds
19 | ErrInsufficientVaultBalance
20 |
21 | // Invalid time authority
22 | ErrInvalidTimeAuthority
23 |
24 | // Invalid vault owner
25 | ErrInvalidVaultOwner
26 |
27 | // Invalid close authority
28 | ErrInvalidCloseAuthority
29 |
30 | // Invalid token balance. Token balance must be zero.
31 | ErrNonZeroTokenBalance
32 |
33 | // Invalid dust burn
34 | ErrInvalidDustBurn
35 |
36 | // Invalid token mint
37 | ErrInvalidTokenMint
38 | )
39 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/legacy_test.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | // todo: updated set of tests from production data
4 |
--------------------------------------------------------------------------------
/pkg/solana/timelock/v1/types.go:
--------------------------------------------------------------------------------
1 | package timelock_token
2 |
3 | type TimelockState uint8
4 |
5 | const (
6 | StateUnknown TimelockState = iota
7 | StateUnlocked
8 | StateWaitingForTimeout
9 | StateLocked
10 | StateClosed
11 | )
12 |
13 | func putTimelockAccountState(dst []byte, v TimelockState, offset *int) {
14 | putUint8(dst, uint8(v), offset)
15 | }
16 |
17 | func getTimelockAccountState(src []byte, dst *TimelockState, offset *int) {
18 | var v uint8
19 | getUint8(src, &v, offset)
20 | *dst = TimelockState(v)
21 | }
22 |
23 | type TimelockDataVersion uint8
24 |
25 | const (
26 | UnknownDataVersion TimelockDataVersion = iota
27 | DataVersionLegacy
28 | DataVersionClosed
29 | DataVersion1
30 | )
31 |
32 | func putTimelockAccountDataVersion(dst []byte, v TimelockDataVersion, offset *int) {
33 | putUint8(dst, uint8(v), offset)
34 | }
35 |
36 | func getTimelockAccountDataVersion(src []byte, dst *TimelockDataVersion, offset *int) {
37 | var v uint8
38 | getUint8(src, &v, offset)
39 | *dst = TimelockDataVersion(v)
40 | }
41 |
42 | func (s TimelockState) String() string {
43 | switch s {
44 | case StateUnknown:
45 | return "unknown"
46 | case StateUnlocked:
47 | return "unlocked"
48 | case StateWaitingForTimeout:
49 | return "waiting_for_timeout"
50 | case StateLocked:
51 | return "locked"
52 | case StateClosed:
53 | return "closed"
54 | }
55 |
56 | return "unknown"
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/sync/ring.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "encoding/binary"
5 |
6 | "github.com/emirpasic/gods/maps/treemap"
7 | "github.com/emirpasic/gods/utils"
8 | "github.com/spaolacci/murmur3"
9 | )
10 |
11 | // ring is a consistent hash ring
12 | //
13 | // todo: extract into it's own package if there becomes more uses with more
14 | // options like hash func
15 | type ring struct {
16 | hashRing *treemap.Map
17 |
18 | // minEntryValue is used to cache the value of min entry in hashRing.
19 | // Using treemap.Map.Min() is O(log n).
20 | minEntryValue interface{}
21 | }
22 |
23 | // newRing returns a new consistent hash ring with the set of entries that have
24 | // replicationFactor entries in the ring
25 | func newRing(entries map[string]interface{}, replicationFactor uint) *ring {
26 | hashRing := treemap.NewWith(utils.Int64Comparator)
27 | for k, v := range entries {
28 | keyHash, _ := murmur3.Sum128([]byte(k))
29 | keyHashBytes := make([]byte, 8)
30 | binary.LittleEndian.PutUint64(keyHashBytes, keyHash)
31 | for i := 0; i < int(replicationFactor); i++ {
32 | hasher := murmur3.New128()
33 | hasher.Write(keyHashBytes)
34 | indexBytes := make([]byte, 4)
35 | binary.LittleEndian.PutUint32(indexBytes, uint32(i))
36 | hasher.Write(indexBytes)
37 | hash, _ := hasher.Sum128()
38 | hashRing.Put(int64(hash), v)
39 | }
40 | }
41 |
42 | _, minEntryValue := hashRing.Min()
43 |
44 | return &ring{
45 | hashRing: hashRing,
46 | minEntryValue: minEntryValue,
47 | }
48 | }
49 |
50 | // shard consistently hashes the key and returns the sharded entry value
51 | func (r *ring) shard(key []byte) interface{} {
52 | hasher := murmur3.New128()
53 | hasher.Write(key)
54 | raw, _ := hasher.Sum128()
55 | hash := int64(raw)
56 | _, shard := r.hashRing.Ceiling(hash)
57 | if shard != nil {
58 | return shard
59 | }
60 | return r.minEntryValue
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/sync/ring_test.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestRing_Consistency(t *testing.T) {
12 | entries := make(map[string]interface{})
13 | for i := 0; i < 64; i++ {
14 | entries[fmt.Sprintf("entry%d", i)] = i
15 | }
16 |
17 | r := newRing(entries, 200)
18 |
19 | for i := 0; i < 256; i++ {
20 | key := []byte(fmt.Sprintf("key%d", i))
21 | val := r.shard(key)
22 |
23 | for j := 0; j < 256; j++ {
24 | assert.EqualValues(t, val, r.shard(key))
25 | }
26 | }
27 | }
28 |
29 | func TestRing_Distribution(t *testing.T) {
30 | entryCount := 5
31 | iterations := 500000
32 | marginOfError := 0.1
33 | expectedFrequency := iterations / entryCount
34 |
35 | entries := make(map[string]interface{})
36 | for i := 0; i < entryCount; i++ {
37 | entries[fmt.Sprintf("entry%d", i)] = i
38 | }
39 |
40 | r := newRing(entries, 200)
41 |
42 | hits := make(map[int]int)
43 | for i := 0; i < iterations; i++ {
44 | key := []byte(fmt.Sprintf("key%d", i))
45 | val := r.shard(key).(int)
46 | hits[val]++
47 | }
48 |
49 | assert.EqualValues(t, entryCount, len(hits))
50 | for _, hitCount := range hits {
51 | assert.True(t, math.Abs(float64(hitCount-expectedFrequency)) <= marginOfError*float64(expectedFrequency))
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/sync/striped_channel_test.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "github.com/stretchr/testify/require"
9 | )
10 |
11 | func TestStripedChannel_HappyPath(t *testing.T) {
12 | c := NewStripedChannel(32, 256)
13 |
14 | channels := c.GetChannels()
15 | require.Equal(t, 32, len(channels))
16 |
17 | results := make([]map[int]int, len(channels))
18 |
19 | var wg sync.WaitGroup
20 | worker := func(id int, c <-chan interface{}) {
21 | defer func() {
22 | wg.Done()
23 | }()
24 |
25 | for {
26 | select {
27 | case val, ok := <-c:
28 | if !ok {
29 | return
30 | }
31 |
32 | results[id][val.(int)]++
33 | }
34 | }
35 | }
36 |
37 | for i, channel := range channels {
38 | wg.Add(1)
39 | results[i] = make(map[int]int)
40 | go worker(i, channel)
41 | }
42 |
43 | for i := 0; i < 256; i++ {
44 | for j := 0; j < 10; j++ {
45 | assert.True(t, c.Send([]byte{byte(i)}, i))
46 | }
47 | }
48 |
49 | c.Close()
50 |
51 | wg.Wait()
52 |
53 | aggregated := make(map[int]int)
54 | for _, result := range results {
55 | for k, v := range result {
56 | aggregated[k] = v
57 | }
58 | }
59 |
60 | assert.Equal(t, 256, len(aggregated))
61 | for _, v := range aggregated {
62 | assert.Equal(t, 10, v)
63 | }
64 | }
65 |
66 | func TestStripedChannel_FullQueue(t *testing.T) {
67 | c := NewStripedChannel(32, 256)
68 |
69 | for i := 0; i < 256; i++ {
70 | assert.True(t, c.Send([]byte{byte(1)}, 1))
71 | }
72 |
73 | assert.False(t, c.Send([]byte{byte(1)}, 1))
74 | assert.True(t, c.Send([]byte{byte(2)}, 2))
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/sync/striped_lock.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "fmt"
5 | base "sync"
6 | )
7 |
8 | const (
9 | hashEntriesPerLock = 200
10 | )
11 |
12 | // StripedLock is a partitioned locking mechanism that consistently maps a key
13 | // space to a set of locks. This provides concurrent data access while also
14 | // limiting the total memory footprint.
15 | type StripedLock struct {
16 | locks []base.RWMutex
17 | hashRing *ring
18 | }
19 |
20 | // NewStripedLock returns a new StripedLock with a static number of stripes.
21 | func NewStripedLock(stripes uint) *StripedLock {
22 | return newStripedLockGroup(stripes, 1)[0]
23 | }
24 |
25 | // newStripedLockGroup returns a new group of StripedLocks which share a common
26 | // hash ring. This should be used in cases where multiple striped locks are
27 | // needed which share the same sharding domain in order to avoid allocating
28 | // entire hash rings for each.
29 | //
30 | // todo: Add tests, then expose publicly
31 | func newStripedLockGroup(stripes, groupSize uint) []*StripedLock {
32 | stripedLocks := make([]*StripedLock, 0, groupSize)
33 |
34 | ringEntries := make(map[string]interface{})
35 | for i := 0; i < int(stripes); i++ {
36 | ringEntries[fmt.Sprintf("lock%d", i)] = i
37 | }
38 |
39 | ring := newRing(ringEntries, hashEntriesPerLock)
40 |
41 | for i := 0; i < int(groupSize); i++ {
42 | stripedLocks = append(stripedLocks, &StripedLock{
43 | locks: make([]base.RWMutex, stripes),
44 | hashRing: ring,
45 | })
46 | }
47 |
48 | return stripedLocks
49 | }
50 |
51 | // Get gets the lock for a key
52 | func (l *StripedLock) Get(key []byte) *base.RWMutex {
53 | sharded := l.hashRing.shard(key).(int)
54 | return &l.locks[sharded]
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/sync/striped_lock_test.go:
--------------------------------------------------------------------------------
1 | package sync
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | base "sync"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestStripedLock_HappyPath(t *testing.T) {
13 | workerCount := 256
14 | operationCount := 100000
15 |
16 | l := NewStripedLock(4)
17 |
18 | var workerWg base.WaitGroup
19 | startChan := make(chan struct{}, 0)
20 | data := make([]int, workerCount)
21 |
22 | for i := 0; i < workerCount; i++ {
23 | workerWg.Add(1)
24 |
25 | go func(workerID int) {
26 | defer workerWg.Done()
27 |
28 | var opWg sync.WaitGroup
29 | key := []byte(fmt.Sprintf("worker%d", workerID))
30 | for j := 0; j < operationCount; j++ {
31 | opWg.Add(1)
32 |
33 | go func() {
34 | defer opWg.Done()
35 |
36 | select {
37 | case <-startChan:
38 | }
39 |
40 | mu := l.Get([]byte(key))
41 | mu.Lock()
42 | data[workerID]++
43 | mu.Unlock()
44 | }()
45 | }
46 | opWg.Wait()
47 | }(i)
48 | }
49 |
50 | close(startChan)
51 | workerWg.Wait()
52 |
53 | for _, val := range data {
54 | assert.EqualValues(t, operationCount, val)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/testutil/error.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "github.com/stretchr/testify/require"
8 | "google.golang.org/grpc/codes"
9 | "google.golang.org/grpc/status"
10 | )
11 |
12 | // AssertStatusErrorWithCode verifies that the provided error is a gRPC status
13 | // error of the provided status code.
14 | func AssertStatusErrorWithCode(t *testing.T, err error, code codes.Code) {
15 | require.Error(t, err)
16 | status, ok := status.FromError(err)
17 | require.True(t, ok)
18 | assert.Equal(t, code, status.Code())
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/testutil/logging.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 |
7 | "github.com/sirupsen/logrus"
8 | )
9 |
10 | func init() {
11 | var isVerbose bool
12 | for _, arg := range os.Args {
13 | if arg == "-test.v=true" {
14 | isVerbose = true
15 | }
16 | }
17 |
18 | logrus.SetLevel(logrus.TraceLevel)
19 |
20 | if !isVerbose {
21 | logrus.StandardLogger().Out = ioutil.Discard
22 | }
23 | }
24 |
25 | func DisableLogging() (reset func()) {
26 | originalLogOutput := logrus.StandardLogger().Out
27 | logrus.StandardLogger().Out = ioutil.Discard
28 | return func() {
29 | logrus.StandardLogger().Out = originalLogOutput
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/testutil/solana.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "crypto/ed25519"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 |
9 | "github.com/stickychin/code-server/pkg/code/common"
10 | )
11 |
12 | func GenerateSolanaKeypair(t *testing.T) ed25519.PrivateKey {
13 | _, p, err := ed25519.GenerateKey(nil)
14 | require.NoError(t, err)
15 | return p
16 | }
17 |
18 | func GenerateSolanaKeys(t *testing.T, n int) []ed25519.PublicKey {
19 | keys := make([]ed25519.PublicKey, n)
20 | for i := 0; i < n; i++ {
21 | p, _, err := ed25519.GenerateKey(nil)
22 | require.NoError(t, err)
23 | keys[i] = p
24 | }
25 | return keys
26 | }
27 |
28 | func NewRandomAccount(t *testing.T) *common.Account {
29 | account, err := common.NewRandomAccount()
30 | require.NoError(t, err)
31 |
32 | return account
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/testutil/subsidizer.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 |
9 | "github.com/stickychin/code-server/pkg/code/common"
10 | code_data "github.com/stickychin/code-server/pkg/code/data"
11 | )
12 |
13 | func SetupRandomSubsidizer(t *testing.T, data code_data.Provider) *common.Account {
14 | account := NewRandomAccount(t)
15 | require.NoError(t, common.InjectTestSubsidizer(context.Background(), data, account))
16 | return account
17 | }
18 |
--------------------------------------------------------------------------------
/pkg/testutil/wait.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/pkg/errors"
7 | )
8 |
9 | // WaitFor waits for a condition to be met before the specified timeout
10 | func WaitFor(timeout, interval time.Duration, condition func() bool) error {
11 | if timeout < interval {
12 | return errors.New("timeout must be greater than interval")
13 | }
14 | start := time.Now()
15 | for {
16 | if condition() {
17 | return nil
18 | }
19 | if time.Since(start) >= timeout {
20 | return errors.Errorf("condition not met withind %v", timeout)
21 | }
22 |
23 | time.Sleep(interval)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/testutil/wait_test.go:
--------------------------------------------------------------------------------
1 | package testutil
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestWaitFor(t *testing.T) {
11 | require.NoError(t, WaitFor(50*time.Millisecond, 25*time.Millisecond, func() bool {
12 | return true
13 | }))
14 |
15 | require.Error(t, WaitFor(50*time.Millisecond, 25*time.Millisecond, func() bool {
16 | return false
17 | }))
18 |
19 | require.Error(t, WaitFor(50*time.Millisecond, 100*time.Millisecond, func() bool {
20 | return true
21 | }))
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/usdc/usdc.go:
--------------------------------------------------------------------------------
1 | package usdc
2 |
3 | import (
4 | "crypto/ed25519"
5 | )
6 |
7 | const (
8 | Mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
9 | QuarksPerUsdc = 1000000
10 | Decimals = 6
11 | )
12 |
13 | var (
14 | TokenMint = ed25519.PublicKey{198, 250, 122, 243, 190, 219, 173, 58, 61, 101, 243, 106, 171, 201, 116, 49, 177, 187, 228, 194, 210, 246, 224, 228, 124, 166, 2, 3, 69, 47, 93, 97}
15 | )
16 |
--------------------------------------------------------------------------------
/pkg/usdg/usdg.go:
--------------------------------------------------------------------------------
1 | package usdg
2 |
3 | const (
4 | Mint = "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH"
5 | QuarksPerUsdg = 1000000
6 | Decimals = 6
7 | )
8 |
--------------------------------------------------------------------------------
/pkg/usdt/usdt.go:
--------------------------------------------------------------------------------
1 | package usdt
2 |
3 | const (
4 | Mint = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
5 | QuarksPerUsdt = 1000000
6 | Decimals = 6
7 | )
8 |
--------------------------------------------------------------------------------